diff --git a/README.md b/README.md index 4a46bc2..c821cf8 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,8 @@ # viz1090 +![demo gif](https://media.giphy.com/media/VGh0nJHerUFFNxeAZo/giphy.gif) + + **This is work in progress** @@ -42,21 +45,17 @@ make clean; make ``` 3. Download and process map data -Grab a shapefile with your desired level of detail from https://www.naturalearthdata.com/downloads - -[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 geopandas tqdm -python3 mapconverter.py ne_10m_admin_1_states_provinces.shp +pip3 install fiona tqdm +./getmap.sh ``` -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. +This will produce files for map and airport geometry, with labels, that viz1090 reads. If any of these files don't exist then visualizer will show planes and trails without any geography. + +The default parameters for mapconverter should render resonably quickly on a Raspberri Pi 4. See the mapconverter section below for other options and more information about map sources. -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) @@ -99,6 +98,31 @@ viz1090 will open an SDL window set to the resolution of your screen. | --uiscale [scale] | Scale up UI elements by integer amounts for high resolution screen | | --fullscreen | Render fullscreen rather than in a window | +### MAPS + +The best map data source I've found so far is https://www.naturalearthdata.com. This has a lot of useful GIS data, but not airport runways, which you can get from the FAA Aeronautical Data Delivery Service (https://adds-faa.opendata.arcgis.com/) + + +I've been using these files: + +* [Map geometry](https://www.naturalearthdata.com/http//www.naturalearthdata.com/download/10m/cultural/ne_10m_admin_1_states_provinces.zip) +* [Place names](https://www.naturalearthdata.com/http//www.naturalearthdata.com/download/10m/cultural/ne_10m_populated_places.zip) +* [Airport IATA codes](https://www.naturalearthdata.com/http//www.naturalearthdata.com/download/10m/cultural/ne_10m_airports.zip) +* [Airport runway geometry](https://opendata.arcgis.com/datasets/4d8fa46181aa470d809776c57a8ab1f6_0.zip) + +The bash script getmap.sh will download (so long as the links don't break) and convert these. Alternatiely, you can pass shapefiles and other arguments to mapconverter.py directly + +### MAPCONVERTER.PY RUNTIME OPTIONS + +| Argument | Description | +| ----------------------------- | ----------- | +| --mapfile | shapefile for main map | +| --mapnames | shapefile for map place names | +| --airportfile | shapefile for airport runway outlines | +| --airportnames | shapefile for airport IATA names | +| --minpop | minimum population to show place names for (defaults to 100000) | +| --tolerance" | map simplification tolerance (defaults to 0.001, which works well on a Raspberry Pi 4 - smaller values will produce more detail but slow down the map refresh rate) | + ### HARDWARE NOTES This software was originally intended for Raspberry Pi devices, and it is currently optimized for the Raspberry Pi 4 with the following configuration: diff --git a/getmap.sh b/getmap.sh index 8f5b5c6..3567c05 100755 --- a/getmap.sh +++ b/getmap.sh @@ -1,5 +1,12 @@ #!/bin/bash wget https://www.naturalearthdata.com/http//www.naturalearthdata.com/download/10m/cultural/ne_10m_admin_1_states_provinces.zip -unzip ne_10m_admin_1_states_provinces.zip -python3 mapconverter.py ne_10m_admin_1_states_provinces.shp +wget https://www.naturalearthdata.com/http//www.naturalearthdata.com/download/10m/cultural/ne_10m_populated_places.zip +wget https://www.naturalearthdata.com/http//www.naturalearthdata.com/download/10m/cultural/ne_10m_airports.zip + +#this may not be up to date +wget https://opendata.arcgis.com/datasets/4d8fa46181aa470d809776c57a8ab1f6_0.zip + +unzip '*.zip' + +python3 mapconverter.py --mapfile ne_10m_admin_1_states_provinces.shp --mapnames ne_10m_populated_places.shp --airportfile Runways.shp --airportnames ne_10m_airports.shp \ No newline at end of file diff --git a/mapconverter-cartopy.py b/mapconverter-cartopy.py deleted file mode 100644 index 78a7233..0000000 --- a/mapconverter-cartopy.py +++ /dev/null @@ -1,64 +0,0 @@ -import cartopy.io.shapereader as shpreader -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 - -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 = [] - - for i in tqdm(range(len(shapefile))): - - simplified = shapefile[i].geometry.simplify(tolerance, preserve_topology=False) - - if(simplified.geom_type == "LineString"): - outlist.extend(convertLinestring(simplified)) - - elif(simplified.geom_type == "MultiPolygon" or simplified.geom_type == "Polygon"): - - if(simplified.boundary.geom_type == "MultiLineString"): - for boundary in simplified.boundary: - outlist.extend(convertLinestring(boundary)) - else: - outlist.extend(convertLinestring(simplified.boundary)) - - else: - print("Unsupported type: " + simplified.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 = list(shpreader.Reader(args.mapfile).records()) - -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)) \ No newline at end of file diff --git a/mapconverter-fiona-airports.py b/mapconverter-fiona-airports.py deleted file mode 100644 index 0929813..0000000 --- a/mapconverter-fiona-airports.py +++ /dev/null @@ -1,85 +0,0 @@ -import fiona -from shapely.geometry import shape -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 - -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 = [] - - for i in tqdm(range(len(shapefile))): - - simplified = shape(shapefile[i]['geometry'])#.simplify(tolerance, preserve_topology=False) - - if(simplified.geom_type == "LineString"): - outlist.extend(convertLinestring(simplified)) - - elif(simplified.geom_type == "MultiPolygon" or simplified.geom_type == "Polygon"): - - if(simplified.boundary.geom_type == "MultiLineString"): - for boundary in simplified.boundary: - outlist.extend(convertLinestring(boundary)) - else: - outlist.extend(convertLinestring(simplified.boundary)) - - else: - print("Unsupported type: " + simplified.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 = fiona.open(args.mapfile) - -outlist = extractLines(shapefile, args.tolerance) - -bin_file = open("airportdata.bin", "wb") -np.asarray(outlist).astype(np.single).tofile(bin_file) -bin_file.close() - -print("Wrote %d points" % (len(outlist) / 2)) - -bin_file = open("airportnames", "w") - -shapefile = fiona.open("ne_10m_airports.shp") - -count = 0 - -for i in tqdm(range(len(shapefile))): - - xcoord = shapefile[i]['geometry']['coordinates'][0] - ycoord = shapefile[i]['geometry']['coordinates'][1] - name = shapefile[i]['properties']['iata_code'] - - outstring = "{0} {1} {2}\n".format(xcoord, ycoord, name) - bin_file.write(outstring) - count = count + 1 - -bin_file.close() - -print("Wrote %d airport names" % count) \ No newline at end of file diff --git a/mapconverter-fiona-name.py b/mapconverter-fiona-name.py deleted file mode 100644 index 317fab6..0000000 --- a/mapconverter-fiona-name.py +++ /dev/null @@ -1,35 +0,0 @@ -import fiona -from tqdm import tqdm -import argparse -import os - - - -bin_file = open("mapnames", "w") - -parser = argparse.ArgumentParser(description='viz1090 Natural Earth Data Map Converter') -parser.add_argument("--minpop", default=100000, type=int, 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 = fiona.open(args.mapfile) - -count = 0 - -for i in tqdm(range(len(shapefile))): - - xcoord = shapefile[i]['geometry']['coordinates'][0] - ycoord = shapefile[i]['geometry']['coordinates'][1] - pop = shapefile[i]['properties']['POP_MIN'] - name = shapefile[i]['properties']['NAME'] - - if pop > args.minpop: - outstring = "{0} {1} {2}\n".format(xcoord, ycoord, name) - bin_file.write(outstring) - count = count + 1 - -bin_file.close() - -print("Wrote %d place names" % count) \ No newline at end of file diff --git a/mapconverter-fiona.py b/mapconverter-fiona.py deleted file mode 100644 index 6f07c29..0000000 --- a/mapconverter-fiona.py +++ /dev/null @@ -1,65 +0,0 @@ -import fiona -from shapely.geometry import shape -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 - -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 = [] - - for i in tqdm(range(len(shapefile))): - - simplified = shape(shapefile[i]['geometry']).simplify(tolerance, preserve_topology=False) - - if(simplified.geom_type == "LineString"): - outlist.extend(convertLinestring(simplified)) - - elif(simplified.geom_type == "MultiPolygon" or simplified.geom_type == "Polygon"): - - if(simplified.boundary.geom_type == "MultiLineString"): - for boundary in simplified.boundary: - outlist.extend(convertLinestring(boundary)) - else: - outlist.extend(convertLinestring(simplified.boundary)) - - else: - print("Unsupported type: " + simplified.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 = fiona.open(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)) \ No newline at end of file diff --git a/mapconverter.py b/mapconverter.py index 8ac8904..d407474 100644 --- a/mapconverter.py +++ b/mapconverter.py @@ -1,4 +1,5 @@ -import geopandas +import fiona +from shapely.geometry import shape import numpy as np from tqdm import tqdm import zipfile @@ -23,40 +24,110 @@ def convertLinestring(linestring): 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])) + for i in tqdm(range(len(shapefile))): + + if(tolerance > 0): + simplified = shape(shapefile[i]['geometry']).simplify(tolerance, preserve_topology=False) + else: + simplified =shape(shapefile[i]['geometry']) + + if(simplified.geom_type == "LineString"): + outlist.extend(convertLinestring(simplified)) - elif(simplified[i].geom_type == "MultiPolygon" or simplified[i].geom_type == "Polygon"): + elif(simplified.geom_type == "MultiPolygon" or simplified.geom_type == "Polygon"): - if(simplified[i].boundary.geom_type == "MultiLineString"): - for boundary in simplified[i].boundary: + if(simplified.boundary.geom_type == "MultiLineString"): + for boundary in simplified.boundary: outlist.extend(convertLinestring(boundary)) else: - outlist.extend(convertLinestring(simplified[i].boundary)) + outlist.extend(convertLinestring(simplified.boundary)) else: - print("Unsupported type: " + simplified[i].geom_type) + print("Unsupported type: " + simplified.geom_type) return outlist parser = argparse.ArgumentParser(description='viz1090 Natural Earth Data Map Converter') +parser.add_argument("--mapfile", type=str, help="shapefile for main map") +parser.add_argument("--mapnames", type=str, help="shapefile for map place names") +parser.add_argument("--airportfile", type=str, help="shapefile for airport runway outlines") +parser.add_argument("--airportnames", type=str, help="shapefile for airport IATA names") +parser.add_argument("--minpop", default=100000, type=int, help="map simplification tolerance") 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) +# mapfile +if args.mapfile is not None: + shapefile = fiona.open(args.mapfile) -outlist = extractLines(shapefile, args.tolerance) + outlist = extractLines(shapefile, args.tolerance) -bin_file = open("mapdata.bin", "wb") -np.asarray(outlist).astype(np.single).tofile(bin_file) -bin_file.close() + 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)) + +# mapnames +bin_file = open("mapnames", "w") + +if args.mapnames is not None: + shapefile = fiona.open(args.mapnames) + + count = 0 + + for i in tqdm(range(len(shapefile))): + + xcoord = shapefile[i]['geometry']['coordinates'][0] + ycoord = shapefile[i]['geometry']['coordinates'][1] + pop = shapefile[i]['properties']['POP_MIN'] + name = shapefile[i]['properties']['NAME'] + + if pop > args.minpop: + outstring = "{0} {1} {2}\n".format(xcoord, ycoord, name) + bin_file.write(outstring) + count = count + 1 + + bin_file.close() + + print("Wrote %d place names" % count) + +#airportfile +if args.airportfile is not None: + shapefile = fiona.open(args.airportfile) + + outlist = extractLines(shapefile, 0) + + bin_file = open("airportdata.bin", "wb") + np.asarray(outlist).astype(np.single).tofile(bin_file) + bin_file.close() + + print("Wrote %d points" % (len(outlist) / 2)) + + +#airportnames +if args.airportnames is not None: + bin_file = open("airportnames", "w") + + shapefile = fiona.open(args.airportnames) + + count = 0 + + for i in tqdm(range(len(shapefile))): + + xcoord = shapefile[i]['geometry']['coordinates'][0] + ycoord = shapefile[i]['geometry']['coordinates'][1] + name = shapefile[i]['properties']['iata_code'] + + outstring = "{0} {1} {2}\n".format(xcoord, ycoord, name) + bin_file.write(outstring) + count = count + 1 + + bin_file.close() + + print("Wrote %d airport names" % count) -print("Wrote %d points" % (len(outlist) / 2)) \ No newline at end of file