From 5afa90b2dc7c44b4ae3d75c202c681a727cbbd4e Mon Sep 17 00:00:00 2001 From: nathan Date: Thu, 18 Jun 2020 19:29:45 -0700 Subject: [PATCH] working natural earth mapconverter --- Map.cpp | 30 +++++------ README.md | 41 +++++++-------- View.cpp | 39 ++++++++------ mapconverter.py | 137 +++++++++++++++++------------------------------- 4 files changed, 106 insertions(+), 141 deletions(-) diff --git a/Map.cpp b/Map.cpp index 238f8c3..8c1b4e0 100644 --- a/Map.cpp +++ b/Map.cpp @@ -4,14 +4,14 @@ bool Map::QTInsert(QuadTree *tree, Line *line, int depth) { - if(depth > 25) { - // printf("fail [%f %f] -> [%f %f]\n",line->start.lon,line->start.lat,line->end.lon,line->end.lat); + // if(depth > 25) { + // // 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); - // fflush(stdout); - tree->lines.push_back(&(*line)); - return true; - } + // // printf("bounds %f %f %f %f\n",tree->lon_min, tree->lon_max, tree->lat_min, tree->lat_max); + // // fflush(stdout); + // tree->lines.push_back(&(*line)); + // return true; + // } 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_max; - if (!startInside || !endInside) { - return false; - } - // if (!startInside && !endInside) { + // if (!startInside || !endInside) { // return false; // } + if (!startInside && !endInside) { + return false; + } - // if (startInside != endInside) { - // tree->lines.push_back(&(*line)); - // return true; - // } + if (startInside != endInside) { + tree->lines.push_back(&(*line)); + return true; + } if (tree->nw == NULL) { tree->nw = new QuadTree; diff --git a/README.md b/README.md index 9313915..4a46bc2 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,20 @@ # viz1090 **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!) * Integration with handheld features like GPS, battery monitors, buttons/dials, etc. -* Android build is currently broken ### BUILDING @@ -34,30 +42,21 @@ make clean; make ``` 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 -pip3 install lxml numpy tqdm -./getmap.sh +pip3 install geopandas tqdm +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: - -``` -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 -``` +The default parameters for mapconverter should render resonably quickly on a Raspberri Pi 4. See the mapconverter section below for other options. 3. (Windows only) diff --git a/View.cpp b/View.cpp index 5b04dda..92028ca 100644 --- a/View.cpp +++ b/View.cpp @@ -292,7 +292,7 @@ void View::font_init() { // 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 lightblue = {211,208,203,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); } - //Debug quadtree - int tl_x,tl_y,tr_x,tr_y,bl_x,bl_y,br_x,br_y; - float dx,dy; + // //Debug quadtree + // int tl_x,tl_y,tr_x,tr_y,bl_x,bl_y,br_x,br_y; + // float dx,dy; - pxFromLonLat(&dx, &dy, tree->lat_min, tree->lon_min); - screenCoords(&tl_x, &tl_y, dx, dy); + // pxFromLonLat(&dx, &dy, tree->lon_min, tree->lat_min); + // screenCoords(&tl_x, &tl_y, dx, dy); - pxFromLonLat(&dx, &dy, tree->lat_max, tree->lon_min); - screenCoords(&tr_x, &tr_y, dx, dy); + // pxFromLonLat(&dx, &dy, tree->lon_max, tree->lat_min); + // screenCoords(&tr_x, &tr_y, dx, dy); - pxFromLonLat(&dx, &dy, tree->lat_min, tree->lon_max); - screenCoords(&bl_x, &bl_y, dx, dy); + // pxFromLonLat(&dx, &dy, tree->lon_min, tree->lat_max); + // screenCoords(&bl_x, &bl_y, dx, dy); - pxFromLonLat(&dx, &dy, tree->lat_max, tree->lon_max); - screenCoords(&br_x, &br_y, dx, dy); + // pxFromLonLat(&dx, &dy, tree->lon_max, tree->lat_max); + // screenCoords(&br_x, &br_y, dx, dy); - lineRGBA(renderer, tl_x, tl_y, tr_x, tr_y, 255, 0, 0, 255); - lineRGBA(renderer, tr_x, tr_y, br_x, br_y, 255, 0, 0, 255); - lineRGBA(renderer, bl_x, bl_y, br_x, br_y, 255, 0, 0, 255); - lineRGBA(renderer, tl_x, tl_y, bl_x, bl_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, 50, 50, 50, 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, 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); + + } diff --git a/mapconverter.py b/mapconverter.py index d139dd5..8ac8904 100644 --- a/mapconverter.py +++ b/mapconverter.py @@ -1,103 +1,62 @@ import geopandas import numpy as np from tqdm import tqdm +import zipfile +from io import BytesIO +#from urllib.request import urlopen +import requests +import argparse +import os -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] +def convertLinestring(linestring): + outlist = [] + pointx = linestring.coords.xy[0] + pointy = linestring.coords.xy[1] + for j in range(len(pointx)): outlist.extend([float(pointx[j]),float(pointy[j])]) 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") np.asarray(outlist).astype(np.single).tofile(bin_file) bin_file.close() -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)) +print("Wrote %d points" % (len(outlist) / 2)) \ No newline at end of file