-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathgeojson.py
195 lines (163 loc) · 5.21 KB
/
geojson.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
import json
import folium
import jsonpath_ng
from folium.elements import JSCSSMixin
from folium.map import Layer
from jinja2 import Template
from . import common
# ==================== This file contains plain geojson plotting code (mode: 'geojson')
# ==================== geojson mode argument definition
def arguments(parser):
"""
Defines arguments specific to geojson plotting.
"""
parser.add_argument(
"--input_geojson",
type=str,
nargs="?",
default="",
help="path to the geojson file to plot",
)
parser.add_argument(
"--jpath_geojson",
type=str,
nargs="?",
default="",
help="JSON path to the geojson elements (XPATH like,"
+ " see https://goessner.net/articles/JsonPath/,"
+ ' example: "state.routes[*].geojson")',
)
parser.add_argument(
"--output_map",
type=str,
nargs="?",
default=None,
help="Interactive map file path",
)
parser.add_argument(
"--custom_map_tile",
nargs="+",
default=[],
help="add further folium custom map tiles "
+ "(either by name "
+ '[e.g.: "stamenwatercolor"] or '
+ 'by "<url>,<name>,<attribution>" '
+ '[e.g.: "https://{s}.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}{r}.png'
+ ',DarkMatter no labels,OpenStreetMap authors"])',
)
parser.add_argument(
"--style",
dest="style",
action="store_true",
default=False,
help="indicates whether to attempt to apply any style info found",
)
# ==================== geojson plotting specific functionality
class StyledGeoJson(JSCSSMixin, Layer):
"""
Creates a GeoJson which supports simplestyle and maki markers.
source: https://stackoverflow.com/questions/66813862/loosing-geojsons-feature-property-information-when-visualising-by-using-folium
"""
_template = Template(
"""
{% macro script(this, kwargs) %}
var {{ this.get_name() }} = L.geoJson({{ this.data }},
{
useSimpleStyle: true,
useMakiMarkers: true
}
).addTo({{ this._parent.get_name() }});
{% endmacro %}
"""
)
default_js = [
("leaflet-simplestyle", "https://unpkg.com/leaflet-simplestyle"),
]
def __init__(self, data, name=None, overlay=True, control=True, show=True):
super().__init__(name=name, overlay=overlay, control=control, show=show)
self._name = "StyledGeoJson"
self.data = data
def parse(
input_geojson: str,
jpath_geojson: str,
) -> list[dict]:
"""
Parses the geojson data object(s) from the file(s).
"""
# Load json data
content_geojson, _ = common.load_data(input_geojson, "")
# Extract geojsons
if jpath_geojson:
expression = jsonpath_ng.parse(jpath_geojson)
geojsons = [json.loads(match.value) for match in expression.find(content_geojson)]
else:
geojsons = [json.loads(content_geojson)]
return geojsons
def plot(
input_geojson: str,
jpath_geojson: str,
output_map: str,
style: bool,
custom_map_tile: list[str],
):
"""
Plots geojson objects from the given file(s) onto a map.
"""
# Determine base filename
base_name = "plot" # Default for STDIN
if input_geojson:
base_name = input_geojson
# Parse data
geojsons = parse(
input_geojson,
jpath_geojson,
)
# Quit on no points
if len(geojsons) <= 0:
print("no geojson found in given file")
return
# Determine bbox for zooming
bbox_sw, bbox_ne = [90, 180], [-90, -180]
for gj in geojsons:
sw, ne = determine_geojson_bbox(gj)
bbox_sw[0], bbox_sw[1] = min(bbox_sw[0], sw[0]), min(bbox_sw[1], sw[1])
bbox_ne[0], bbox_ne[1] = max(bbox_ne[0], ne[0]), max(bbox_ne[1], ne[1])
# Make map plot of routes
map_file = output_map
if not map_file:
map_file = base_name + ".map.html"
print(f"Plotting map to {map_file}")
m = common.create_map(
(bbox_sw[1] + bbox_ne[1]) / 2.0,
(bbox_sw[0] + bbox_ne[0]) / 2.0,
custom_map_tile,
)
for i, gj in enumerate(geojsons):
group = folium.FeatureGroup(f"geojson {i}")
if style:
StyledGeoJson(gj).add_to(group)
else:
folium.GeoJson(gj).add_to(group)
group.add_to(m)
# Add control for all layers and write file
folium.LayerControl().add_to(m)
m.fit_bounds([sw, ne])
m.save(map_file)
def determine_geojson_bbox(d: dict) -> tuple[tuple[float, float], tuple[float, float]]:
"""
Determine the bounding box of a geojson object.
"""
sw, ne = [90, 180], [-90, -180]
def traverse(d):
if isinstance(d, list) and len(d) == 2 and isinstance(d[0], int | float):
sw[0], sw[1] = min(sw[0], d[1]), min(sw[1], d[0])
ne[0], ne[1] = max(ne[0], d[1]), max(ne[1], d[0])
return
elif isinstance(d, dict):
for value in d.values():
traverse(value)
elif isinstance(d, list):
for value in d:
traverse(value)
traverse(d)
return sw, ne