1 """
2 Generating geojson (RFC 7946) files.
3
4 This requires annotation with the geojson (DaCHS-private) data model.
5
6 See separate documentation in the reference documentation.
7
8 No streaming is forseen for this format for now; whatever a web browser
9 can cope with, we can, too. I hope.
10
11 To add more geometry types, pick a type name (typically different
12 from what geojson calls the thing because it also depends on the input),
13 add it to _FEATURE_MAKERS and write the Factory, taking _getSepcooFactory as
14 a model.
15 """
16
17
18
19
20
21
22
23 import json
24
25 from gavo import base
26 from gavo.formats import common
27 from gavo.utils import serializers
28
29
30
31
32
33 JSON_MF_REGISTRY = serializers.defaultMFRegistry.clone()
34 registerMF = JSON_MF_REGISTRY.registerFactory
35
37 if hasattr(colDesc.original, "geojson_do_not_touch"):
38 return lambda val: val
39 registerMF(_rawMapperFactory)
40
41
43 """returns a dictionary to add a geoJSON CRS structure from a DaCHS
44 annotation to a dictionary.
45 """
46 try:
47 rawCRS = gjAnnotation["crs"]
48 if rawCRS["type"]=="name":
49 return {"crs": {
50 "type": "name",
51 "properties": {
52 "name": rawCRS["properties"]["name"],
53 }
54 }
55 }
56
57 elif rawCRS["type"]=="link":
58 return {"crs": {
59 "type": "url",
60 "properties": {
61 "href": rawCRS["properties"]["href"],
62 "type": rawCRS["properties"]["type"],
63 }
64 }
65 }
66
67 else:
68 raise base.DataError("Unknown GeoJSON CRS type: %s"%rawCRS["type"])
69
70 except KeyError:
71 return {}
72
73
75 """returns a factory function for building geoJson features from
76 rowdicts.
77
78 skippedFields are not included in the properties (i.e., they're
79 what geometry is built from),, geoFactory is a function returning
80 the geometry itself, given a row.
81 """
82 propertiesFields = [f.name for f in tableDef.columns
83 if f.name not in skippedFields]
84
85 def buildFeature(row):
86 feature = geoFactory(row)
87 feature["properties"] = dict((n, row[n]) for n in propertiesFields)
88 return feature
89
90 return buildFeature
91
92
94 """returns a row factory for a geometry-valued column.
95
96 This expects a value key referencing a column typed either
97 spoint or spoly.
98 """
99 geoCol = geometryAnnotation["value"].value
100
101 if geoCol.type=="spoint":
102 def annMaker(row):
103 return {
104 "type": "Point",
105 "coordinates": list(row[geoCol.name].asCooPair())}
106 geoCol.geojson_do_not_touch = True
107
108 elif geoCol.type=="spoly":
109 def annMaker(row):
110 return {
111 "type": "Polygon",
112 "coordinates": [list(p) for p in
113 row[geoCol.name].asCooPairs()]}
114 geoCol.geojson_do_not_touch = True
115
116 else:
117 raise base.DataError("Cannot serialise %s-valued columns"
118 " with a 'geometry' geometry type (only spoint and spoly)")
119
120 return _makeFeatureFactory(tableDef, [geoCol.name], annMaker)
121
122
124 """returns a row factory for polygons specified with a min/max coordinate
125 range.
126
127 This expects c1min/max, c2min/max keys. It does not do anything special
128 if the simplex spans the stitching line.
129 """
130 c1min = geometryAnnotation["c1min"].value.name
131 c1max = geometryAnnotation["c1max"].value.name
132 c2min = geometryAnnotation["c2min"].value.name
133 c2max = geometryAnnotation["c2max"].value.name
134
135 return _makeFeatureFactory(tableDef,
136 [c1min, c2min, c1max, c2max],
137 lambda row: {
138 "type": "Polygon",
139 "coordinates": [
140 [row[c1min], row[c2min]],
141 [row[c1min], row[c2max]],
142 [row[c1max], row[c2max]],
143 [row[c1max], row[c2min]],
144 [row[c1min], row[c2min]]]})
145
146
148 """returns a features factory for polygons made of separate
149 coordinates.
150
151 This expects cn_m keys; it will gooble them up until the first is not
152 found. m is either 1 or 2.
153 """
154
155
156 cooIndex = 1
157 polyCoos, ignoredNames = [], set()
158 while True:
159 try:
160 polyCoos.append((
161 geometryAnnotation["c%d_1"%cooIndex].value.name,
162 geometryAnnotation["c%d_2"%cooIndex].value.name))
163 ignoredNames |= set(polyCoos[-1])
164 except KeyError:
165 break
166 cooIndex += 1
167 polyCoos.append(polyCoos[0])
168
169 return _makeFeatureFactory(tableDef,
170 ignoredNames,
171 lambda row: {
172 "type": "Polygon",
173 "coordinates": [[row[name1], row[name2]]
174 for name1, name2 in polyCoos]})
175
176
178 """returns a features factory for points made up of separate coordinates.
179
180 This expects latitude and longitude keys.
181 """
182
183
184 latCoo = geometryAnnotation["latitude"].value.name
185 longCoo = geometryAnnotation["longitude"].value.name
186 return _makeFeatureFactory(tableDef,
187 [latCoo, longCoo],
188 lambda row: {
189 "type": "Point",
190 "coordinates": [row[longCoo], row[latCoo]]})
191
192
193
194
195 _FEATURE_MAKERS = {
196 "sepcoo": _getSepcooFactory,
197 "seppoly": _getSeppolyFactory,
198 "sepsimplex": _getSepsimplexFactory,
199 "geometry": _getGeometryFactory,
200 }
201
203 """returns a list of geoJSON features from the (annotated) table.
204 """
205 geometryAnnotation = gjAnnotation["feature"]["geometry"]
206 try:
207 makeFeature = _FEATURE_MAKERS[
208 geometryAnnotation["type"]](
209 table.tableDef, geometryAnnotation)
210 except KeyError as msg:
211 raise base.ui.logOldExc(
212 base.DataError("Invalid geoJSON annotation on table %s: %s missing"%(
213 table.tableDef.id, msg)))
214
215 sm = base.SerManager(table, acquireSamples=False,
216 mfRegistry=JSON_MF_REGISTRY)
217
218
219 features = []
220 for r in sm.getMappedValues():
221 try:
222 features.append(makeFeature(r))
223 except base.SkipThis:
224 pass
225 return features
226
228 """writes a table as geojson.
229
230 This requires an annotation with geojson:FeatureCollection.
231 """
232
233
234 if hasattr(table, "getPrimaryTable"):
235 table = table.getPrimaryTable()
236
237 try:
238 ann = table.tableDef.iterAnnotationsOfType("geojson:FeatureCollection"
239 ).next()
240 except IndexError:
241 raise base.DataError("Table has no geojson:FeatureCollection annotation."
242 " Cannot serialise to GeoJSON.")
243
244 result = {
245 "type": "FeatureCollection",
246 "features": _makeFeatures(table, ann),
247 }
248 result.update(_makeCRS(ann))
249
250 return json.dump(result, target, encoding="utf-8")
251
252
253
254
255 common.registerDataWriter("geojson",
256 writeTableAsGeoJSON, "application/geo+json", "GeoJSON", ".geojson")
257