1/*
2 * Copyright 2015 Aerospike, Inc.
3 *
4 * Portions may be licensed to Aerospike, Inc. under one or more
5 * contributor license agreements.
6 *
7 * Licensed under the Apache License, Version 2.0 (the "License"); you
8 * may not use this file except in compliance with the License. You
9 * may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
15 * implied. See the License for the specific language governing
16 * permissions and limitations under the License.
17 */
18
19#include <memory>
20#include <iostream>
21#include <iomanip>
22#include <stdexcept>
23
24#include <jansson.h>
25
26#include <s2.h>
27#include <s2cap.h>
28#include <s2cellid.h>
29#include <s2polygon.h>
30#include <s2regionunion.h>
31#include <s2latlng.h>
32
33#include "geospatial/scoped.h"
34#include "geospatial/throwstream.h"
35#include "geospatial/geojson.h"
36
37using namespace std;
38
39namespace {
40
41S2Point
42traverse_point(json_t * coord)
43{
44 if (! coord) {
45 throwstream(runtime_error, "missing coordinates");
46 }
47
48 if (! json_is_array(coord)) {
49 throwstream(runtime_error, "coordinates are not array");
50 }
51
52 if (json_array_size(coord) != 2) {
53 throwstream(runtime_error, "expected 2 coordinates, saw "
54 << json_array_size(coord));
55 }
56
57 double lngval;
58 json_t * lng = json_array_get(coord, 0);
59 if (json_is_real(lng)) {
60 lngval = json_real_value(lng);
61 }
62 else if (json_is_integer(lng)) {
63 lngval = double(json_integer_value(lng));
64 }
65 else {
66 throwstream(runtime_error, "longitude not numeric value");
67 }
68
69 double latval;
70 json_t * lat = json_array_get(coord, 1);
71 if (json_is_real(lat)) {
72 latval = json_real_value(lat);
73 }
74 else if (json_is_integer(lat)) {
75 latval = double(json_integer_value(lat));
76 }
77 else {
78 throwstream(runtime_error, "latitude not numeric value");
79 }
80
81 // cout << setprecision(15) << latval << ", " << lngval << endl;
82
83 S2LatLng latlng = S2LatLng::FromDegrees(latval, lngval).Normalized();
84 if (! latlng.is_valid()) {
85 throwstream(runtime_error, "invalid latitude-longitude");
86 }
87 return latlng.ToPoint();
88}
89
90S2Loop *
91traverse_loop(json_t * vertices)
92{
93 if (! vertices) {
94 throwstream(runtime_error, "missing vertices");
95 }
96
97 if (! json_is_array(vertices)) {
98 throwstream(runtime_error, "vertices are not array");
99 }
100
101 vector<S2Point> points;
102
103 for (size_t ii = 0; ii < json_array_size(vertices); ++ii) {
104 points.push_back(traverse_point(json_array_get(vertices, ii)));
105 }
106
107 // Remove duplicate points.
108 for (size_t ii = 1; ii < points.size(); ++ii) {
109 if (points[ii - 1] == points[ii]) {
110 points.erase(points.begin() + ii);
111 --ii;
112 }
113 }
114
115 if (points.size() < 4) {
116 throwstream(runtime_error, "loop contains less than 4 points");
117 }
118 if (points[0] != points[points.size()-1]) {
119 throwstream(runtime_error, "loop not closed");
120 }
121 points.pop_back();
122
123 auto_ptr<S2Loop> loop(new S2Loop(points));
124 loop->Normalize();
125 return loop.release();
126}
127
128S2Polygon *
129traverse_polygon(json_t * loops)
130{
131 if (! loops) {
132 throwstream(runtime_error, "missing polygon body");
133 }
134
135 if (! json_is_array(loops)) {
136 throwstream(runtime_error, "polygon body is not array");
137 }
138
139 vector<S2Loop *> loopv;
140 try
141 {
142 for (size_t ii = 0; ii < json_array_size(loops); ++ii) {
143 loopv.push_back(traverse_loop(json_array_get(loops, ii)));
144 }
145
146 return new S2Polygon(&loopv);
147 }
148 catch (...)
149 {
150 for (size_t ii = 0; ii < loopv.size(); ++ii) {
151 delete loopv[ii];
152 }
153 throw;
154 }
155}
156
157void process_point(GeoJSON::GeometryHandler & geohand, json_t * coord)
158{
159 geohand.handle_point(S2CellId::FromPoint(traverse_point(coord)));
160}
161
162void
163process_polygon(GeoJSON::GeometryHandler & geohand, json_t * coord)
164{
165 if (! coord) {
166 throwstream(runtime_error, "missing coordinates");
167 }
168
169 if (! json_is_array(coord)) {
170 throwstream(runtime_error, "coordinates are not array");
171 }
172
173 S2Polygon * poly = traverse_polygon(coord);
174 if (geohand.handle_region(poly)) {
175 delete poly;
176 }
177}
178
179void
180process_multipolygon(GeoJSON::GeometryHandler & geohand, json_t * coord)
181{
182 if (! coord) {
183 throwstream(runtime_error, "missing coordinates");
184 }
185
186 if (! json_is_array(coord)) {
187 throwstream(runtime_error, "coordinates are not array");
188 }
189
190 auto_ptr<S2RegionUnion> regionsp(new S2RegionUnion);
191
192 for (size_t ii = 0; ii < json_array_size(coord); ++ii) {
193 regionsp->Add(traverse_polygon(json_array_get(coord, ii)));
194 }
195
196 if (! geohand.handle_region(regionsp.get())) {
197 // Handler took ownership.
198 regionsp.release();
199 }
200}
201
202void
203process_circle(GeoJSON::GeometryHandler & geohand, json_t * coord)
204{
205 // {
206 // "type": "AeroCircle",
207 // "coordinates": [[-122.097837, 37.421363], 1000.0]
208 // }
209
210 if (! coord) {
211 throwstream(runtime_error, "missing coordinates");
212 }
213
214 if (! json_is_array(coord)) {
215 throwstream(runtime_error, "coordinates are not array");
216 }
217
218 if (json_array_size(coord) != 2) {
219 throwstream(runtime_error, "malformed circle coordinate array");
220 }
221
222 S2Point center = traverse_point(json_array_get(coord, 0));
223
224 double radius;
225 json_t * radiusobj = json_array_get(coord, 1);
226 if (json_is_real(radiusobj)) {
227 radius = json_real_value(radiusobj);
228 }
229 else if (json_is_integer(radiusobj)) {
230 radius = double(json_integer_value(radiusobj));
231 }
232 else {
233 throwstream(runtime_error, "radius not numeric value");
234 }
235
236 S1Angle angle = S1Angle::Radians(radius / geohand.earth_radius_meters());
237
238 auto_ptr<S2Cap> capp(S2Cap::FromAxisAngle(center, angle).Clone());
239
240 if (! geohand.handle_region(capp.get())) {
241 // Handler took ownership.
242 capp.release();
243 }
244}
245
246void traverse_geometry(GeoJSON::GeometryHandler & geohand, json_t * geom)
247{
248 if (! geom) {
249 throwstream(runtime_error, "missing geometry element");
250 }
251
252 if (! json_is_object(geom)) {
253 throwstream(runtime_error, "geometry is not object");
254 }
255
256 json_t * type = json_object_get(geom, "type");
257 if (! type) {
258 throwstream(runtime_error, "missing geometry type");
259 }
260
261 if (! json_is_string(type)) {
262 throwstream(runtime_error, "geometry type is not string");
263 }
264
265 string typestr(json_string_value(type));
266 if (typestr == "Point") {
267 process_point(geohand, json_object_get(geom, "coordinates"));
268 }
269 else if (typestr == "Polygon") {
270 process_polygon(geohand, json_object_get(geom, "coordinates"));
271 }
272 else if (typestr == "MultiPolygon") {
273 process_multipolygon(geohand, json_object_get(geom, "coordinates"));
274 }
275 else if (typestr == "AeroCircle") {
276 process_circle(geohand, json_object_get(geom, "coordinates"));
277 }
278 else {
279 throwstream(runtime_error, "unknown geometry type: " << typestr);
280 }
281}
282
283} // end namespace
284
285namespace GeoJSON {
286
287void GeometryHandler::handle_point(S2CellId const & i_cellid)
288{
289 // nothing by default
290}
291
292bool GeometryHandler::handle_region(S2Region * i_regionp)
293{
294 // By default, caller should delete the region.
295 return true;
296}
297
298void parse(GeometryHandler & geohand, string const & geostr)
299{
300 json_error_t err;
301 Scoped<json_t *> geojson(json_loadb(geostr.data(), geostr.size(), 0, &err),
302 NULL, json_decref);
303 if (! geojson) {
304 throwstream(runtime_error, "failed to parse geojson: "
305 << err.line << ": " << err.text);
306 }
307
308 geohand.set_json(geojson);
309
310 if (! json_is_object(geojson)) {
311 throwstream(runtime_error, "top level geojson element not object");
312 }
313
314 json_t * type = json_object_get(geojson, "type");
315 if (! type) {
316 throwstream(runtime_error, "missing top-level type in geojson element");
317 }
318
319 if (! json_is_string(type)) {
320 throwstream(runtime_error, "top-level type is not string");
321 }
322
323 string typestr(json_string_value(type));
324 if (typestr == "Feature") {
325 traverse_geometry(geohand, json_object_get(geojson, "geometry"));
326 }
327 else if (typestr == "Point") {
328 process_point(geohand, json_object_get(geojson, "coordinates"));
329 }
330 else if (typestr == "Polygon") {
331 process_polygon(geohand, json_object_get(geojson, "coordinates"));
332 }
333 else if (typestr == "MultiPolygon") {
334 process_multipolygon(geohand, json_object_get(geojson, "coordinates"));
335 }
336 else if (typestr == "AeroCircle") {
337 process_circle(geohand, json_object_get(geojson, "coordinates"));
338 }
339 else {
340 throwstream(runtime_error, "unknown top-level type: " << typestr);
341 }
342}
343
344} // end namespace GeoJSON
345