working natural earth mapconverter

This commit is contained in:
nathan 2020-06-18 19:29:45 -07:00
parent a580fbbba1
commit 5afa90b2dc
4 changed files with 106 additions and 141 deletions

30
Map.cpp
View file

@ -4,14 +4,14 @@
bool Map::QTInsert(QuadTree *tree, Line *line, int depth) { bool Map::QTInsert(QuadTree *tree, Line *line, int depth) {
if(depth > 25) { // if(depth > 25) {
// printf("fail [%f %f] -> [%f %f]\n",line->start.lon,line->start.lat,line->end.lon,line->end.lat); // // printf("fail [%f %f] -> [%f %f]\n",line->start.lon,line->start.lat,line->end.lon,line->end.lat);
// printf("bounds %f %f %f %f\n",tree->lon_min, tree->lon_max, tree->lat_min, tree->lat_max); // // printf("bounds %f %f %f %f\n",tree->lon_min, tree->lon_max, tree->lat_min, tree->lat_max);
// fflush(stdout); // // fflush(stdout);
tree->lines.push_back(&(*line)); // tree->lines.push_back(&(*line));
return true; // return true;
} // }
bool startInside = line->start.lat >= tree->lat_min && bool startInside = line->start.lat >= tree->lat_min &&
@ -24,17 +24,17 @@ bool Map::QTInsert(QuadTree *tree, Line *line, int depth) {
line->end.lon >= tree->lon_min && line->end.lon >= tree->lon_min &&
line->end.lon <= tree->lon_max; line->end.lon <= tree->lon_max;
if (!startInside || !endInside) { // if (!startInside || !endInside) {
return false;
}
// if (!startInside && !endInside) {
// return false; // return false;
// } // }
if (!startInside && !endInside) {
return false;
}
// if (startInside != endInside) { if (startInside != endInside) {
// tree->lines.push_back(&(*line)); tree->lines.push_back(&(*line));
// return true; return true;
// } }
if (tree->nw == NULL) { if (tree->nw == NULL) {
tree->nw = new QuadTree; tree->nw = new QuadTree;

View file

@ -1,12 +1,20 @@
# viz1090 # viz1090
**This is work in progress** **This is work in progress**
There are a lot of missing pieces in this implementation so far:
* A proper map system yet. Eventually map data should be pulled from Mapbox or similar.
* In-application menus or configuration yet. There are some major fixes and cleanup that need to happen before a relase:
* Everything is a grab bag of C and C++, need to more consistently modernize
* A full refactor, especially View.cpp, necessary for many of the new features below.
* A working Android build, as this is the best way to run this on portable hardware.
There are also a lot of missing features:
* Map improvements
* Labels, different colors/line weights for features
* Tile prerenderer for improved performance
* In-application menus for view options and configuration
* Theming/colormaps (important as this is primarily intended to be eye candy!) * Theming/colormaps (important as this is primarily intended to be eye candy!)
* Integration with handheld features like GPS, battery monitors, buttons/dials, etc. * Integration with handheld features like GPS, battery monitors, buttons/dials, etc.
* Android build is currently broken
### BUILDING ### BUILDING
@ -34,30 +42,21 @@ make clean; make
``` ```
3. Download and process map data 3. Download and process map data
Until more comprehensive map source (e.g., Mapbox) is integrated, viz1090 uses the lat/lon SVG files from https://www.mccurley.org Grab a shapefile with your desired level of detail from https://www.naturalearthdata.com/downloads
The getmap.sh pulls the large SVG file for the contiguous 48 US states and produces a binary file for viz1090 to read. [This](https://www.naturalearthdata.com/http//www.naturalearthdata.com/download/10m/cultural/ne_10m_admin_1_states_provinces.zip) is a good place to start.
Unzip and copy the .shp and .shx files.
``` ```
sudo apt install python3 python3-pip sudo apt install python3 python3-pip
pip3 install lxml numpy tqdm pip3 install geopandas tqdm
./getmap.sh python3 mapconverter.py ne_10m_admin_1_states_provinces.shp
``` ```
There is also a world map avaiable from McCurley (https://mccurley.org/svg/data/World.svgz), which is much lower resolution and thus better for lower power hardware. This will produce a file mapdata.bin that viz1090 reads. If the file doesn't exist then visualizer will show planes and trails without any geography.
The mapconverter script called by getmap.sh downsamples the file to render resonably quickly on a Raspberri Pi 4. If you are on a slower device (e.g, a Raspberry Pi 3), you may want to try something like: The default parameters for mapconverter should render resonably quickly on a Raspberri Pi 4. See the mapconverter section below for other options.
```
python3 mapconverter.py --resolution 64 all.svg
```
On the other hand, if you are on a modern desktop or laptop, you can use something higher (but you probably don't need the full 6 digit precision of the McCurley SVG file):
```
python3 mapconverter.py --resolution 8192 all.svg
```
3. (Windows only) 3. (Windows only)

View file

@ -292,7 +292,7 @@ void View::font_init() {
// todo separate style stuff // todo separate style stuff
// //
SDL_Color bgcolor = {0,0,0,255}; SDL_Color bgcolor = {0,0,20,255};
SDL_Color greenblue = {236,192,68,255}; SDL_Color greenblue = {236,192,68,255};
SDL_Color lightblue = {211,208,203,255}; SDL_Color lightblue = {211,208,203,255};
SDL_Color mediumblue ={110,136,152,255}; SDL_Color mediumblue ={110,136,152,255};
@ -667,26 +667,33 @@ void View::drawLinesRecursive(QuadTree *tree, float screen_lat_min, float screen
lineRGBA(renderer, x1, y1, x2, y2, style.mapInnerColor.r, style.mapInnerColor.g, style.mapInnerColor.b, 255); lineRGBA(renderer, x1, y1, x2, y2, style.mapInnerColor.r, style.mapInnerColor.g, style.mapInnerColor.b, 255);
} }
//Debug quadtree // //Debug quadtree
int tl_x,tl_y,tr_x,tr_y,bl_x,bl_y,br_x,br_y; // int tl_x,tl_y,tr_x,tr_y,bl_x,bl_y,br_x,br_y;
float dx,dy; // float dx,dy;
pxFromLonLat(&dx, &dy, tree->lat_min, tree->lon_min); // pxFromLonLat(&dx, &dy, tree->lon_min, tree->lat_min);
screenCoords(&tl_x, &tl_y, dx, dy); // screenCoords(&tl_x, &tl_y, dx, dy);
pxFromLonLat(&dx, &dy, tree->lat_max, tree->lon_min); // pxFromLonLat(&dx, &dy, tree->lon_max, tree->lat_min);
screenCoords(&tr_x, &tr_y, dx, dy); // screenCoords(&tr_x, &tr_y, dx, dy);
pxFromLonLat(&dx, &dy, tree->lat_min, tree->lon_max); // pxFromLonLat(&dx, &dy, tree->lon_min, tree->lat_max);
screenCoords(&bl_x, &bl_y, dx, dy); // screenCoords(&bl_x, &bl_y, dx, dy);
pxFromLonLat(&dx, &dy, tree->lat_max, tree->lon_max); // pxFromLonLat(&dx, &dy, tree->lon_max, tree->lat_max);
screenCoords(&br_x, &br_y, dx, dy); // screenCoords(&br_x, &br_y, dx, dy);
lineRGBA(renderer, tl_x, tl_y, tr_x, tr_y, 255, 0, 0, 255); // lineRGBA(renderer, tl_x, tl_y, tr_x, tr_y, 50, 50, 50, 255);
lineRGBA(renderer, tr_x, tr_y, br_x, br_y, 255, 0, 0, 255); // lineRGBA(renderer, tr_x, tr_y, br_x, br_y, 50, 50, 50, 255);
lineRGBA(renderer, bl_x, bl_y, br_x, br_y, 255, 0, 0, 255); // lineRGBA(renderer, bl_x, bl_y, br_x, br_y, 50, 50, 50, 255);
lineRGBA(renderer, tl_x, tl_y, bl_x, bl_y, 255, 0, 0, 255); // lineRGBA(renderer, tl_x, tl_y, bl_x, bl_y, 50, 50, 50, 255);
// // pixelRGBA(renderer,tl_x, tl_y,255,0,0,255);
// pixelRGBA(renderer,tr_x, tr_y,0,255,0,255);
// pixelRGBA(renderer,bl_x, bl_y,0,0,255,255);
// pixelRGBA(renderer,br_x, br_y,255,255,0,255);
} }

View file

@ -1,103 +1,62 @@
import geopandas import geopandas
import numpy as np import numpy as np
from tqdm import tqdm from tqdm import tqdm
import zipfile
from io import BytesIO
#from urllib.request import urlopen
import requests
import argparse
import os
outlist = [] def convertLinestring(linestring):
outlist = []
tolerance = .05
coast = geopandas.read_file("ne_10m_coastline.shp")
coast_simple = coast['geometry'].simplify(tolerance, preserve_topology=False)
for i in tqdm(range(len(coast_simple))):
pointx = coast_simple[i].coords.xy[0]
pointy = coast_simple[i].coords.xy[1]
pointx = linestring.coords.xy[0]
pointy = linestring.coords.xy[1]
for j in range(len(pointx)): for j in range(len(pointx)):
outlist.extend([float(pointx[j]),float(pointy[j])]) outlist.extend([float(pointx[j]),float(pointy[j])])
outlist.extend([0,0]) outlist.extend([0,0])
return outlist
def extractLines(shapefile, tolerance):
print("Extracting map lines")
outlist = []
simplified = shapefile['geometry'].simplify(tolerance, preserve_topology=False)
for i in tqdm(range(len(simplified))):
if(simplified[i].geom_type == "LineString"):
outlist.extend(convertLinestring(simplified[i]))
elif(simplified[i].geom_type == "MultiPolygon" or simplified[i].geom_type == "Polygon"):
if(simplified[i].boundary.geom_type == "MultiLineString"):
for boundary in simplified[i].boundary:
outlist.extend(convertLinestring(boundary))
else:
outlist.extend(convertLinestring(simplified[i].boundary))
else:
print("Unsupported type: " + simplified[i].geom_type)
return outlist
parser = argparse.ArgumentParser(description='viz1090 Natural Earth Data Map Converter')
parser.add_argument("--tolerance", default=0.001, type=float, help="map simplification tolerance")
parser.add_argument("--scale", default="10m", choices=["10m","50m","110m"], type=str, help="map file scale")
parser.add_argument("mapfile", type=str, help="shapefile to load (e.g., from https://www.naturalearthdata.com/downloads/")
args = parser.parse_args()
shapefile = geopandas.read_file(args.mapfile)
outlist = extractLines(shapefile, args.tolerance)
bin_file = open("mapdata.bin", "wb") bin_file = open("mapdata.bin", "wb")
np.asarray(outlist).astype(np.single).tofile(bin_file) np.asarray(outlist).astype(np.single).tofile(bin_file)
bin_file.close() bin_file.close()
print("Wrote %d points" % (len(outlist) / 2)) print("Wrote %d points" % (len(outlist) / 2))
# import json
# import numpy as np
# import sys
# from tqdm import tqdm
# import argparse
# parser = argparse.ArgumentParser(description='viz1090 SVG Map Converter')
# parser.add_argument("--resolution", default=250, type=int, help="downsample resolution")
# parser.add_argument("file", nargs="+", help="filename")
# args = parser.parse_args()
# if(len(args.file) == 0):
# print("No input filename given")
# exit()
# bin_file = open("mapdata.bin", "wb")
# outlist = []
# resolution = args.resolution
# for file in args.file:
# with open(file, "r") as read_file:
# data = json.load(read_file)
# print("Reading points")
# for i in tqdm(range(len(data['features']))):
# if(data['features'][i]['geometry']['type'] == 'LineString'):
# prevx = 0
# prevy = 0
# temp = []
# for currentPoint in data['features'][i]['geometry']['coordinates']:
# currentx = float(int(resolution * float(currentPoint[0]))) / resolution
# currenty = float(int(resolution * float(currentPoint[1]))) / resolution
# if(currentx != prevx or currenty != prevy):
# temp.extend([currentx,currenty])
# prevx = currentx
# prevy = currenty
# temp.extend(["0","0"])
# else:
# prevx = 0
# prevy = 0
# temp = []
# for currentLine in data['features'][i]['geometry']['coordinates']:
# for currentPoint in currentLine:
# currentx = float(int(resolution * float(currentPoint[0]))) / resolution
# currenty = float(int(resolution * float(currentPoint[1]))) / resolution
# if(currentx != prevx or currenty != prevy):
# temp.extend([currentx,currenty])
# prevx = currentx
# prevy = currenty
# temp.extend(["0","0"])
# outlist.extend(temp)
# np.asarray(outlist).astype(np.single).tofile(bin_file)
# bin_file.close()
# print("Wrote %d points" % (len(outlist) / 2))