1/*
2 * geospatial.cpp
3 *
4 * Copyright (C) 2015 Aerospike, Inc.
5 *
6 * Portions may be licensed to Aerospike, Inc. under one or more contributor
7 * license agreements.
8 *
9 * This program is free software: you can redistribute it and/or modify it under
10 * the terms of the GNU Affero General Public License as published by the Free
11 * Software Foundation, either version 3 of the License, or (at your option) any
12 * later version.
13 *
14 * This program is distributed in the hope that it will be useful, but WITHOUT
15 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16 * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
17 * details.
18 *
19 * You should have received a copy of the GNU Affero General Public License
20 * along with this program. If not, see http://www.gnu.org/licenses/
21 */
22
23#include <errno.h>
24#include <limits.h>
25#include <string.h>
26
27#include <stdexcept>
28
29#include <s2regioncoverer.h>
30
31extern "C" {
32#include "fault.h"
33#include "base/datamodel.h"
34} // end extern "C"
35
36#include "geospatial/geospatial.h"
37#include "geospatial/geojson.h"
38
39using namespace std;
40
41class PointRegionHandler: public GeoJSON::GeometryHandler
42{
43public:
44 PointRegionHandler(as_namespace * ns)
45 : m_cellid(0)
46 , m_regionp(NULL)
47 {
48 m_earth_radius_meters =
49 ns ? double(ns->geo2dsphere_within_earth_radius_meters) : 6371000;
50 }
51
52 virtual void handle_point(S2CellId const & cellid) {
53 m_cellid = cellid;
54 }
55
56 virtual bool handle_region(S2Region * regionp) {
57 m_regionp = regionp;
58 return false; // Don't delete this region, please.
59 }
60
61 virtual double earth_radius_meters() {
62 return m_earth_radius_meters;
63 }
64
65 double m_earth_radius_meters;
66 S2CellId m_cellid;
67 S2Region * m_regionp;
68};
69
70bool
71geo_parse(as_namespace * ns,
72 const char * buf,
73 size_t bufsz,
74 uint64_t * cellidp,
75 geo_region_t * regionp)
76{
77 try
78 {
79 PointRegionHandler prhandler(ns);
80 GeoJSON::parse(prhandler, string(buf, bufsz));
81 *cellidp = prhandler.m_cellid.id();
82 *regionp = (geo_region_t) prhandler.m_regionp;
83 return true;
84 }
85 catch (exception const & ex)
86 {
87 cf_warning(AS_GEO, (char *) "failed to parse point: %s", ex.what());
88 return false;
89 }
90}
91
92bool
93geo_region_cover(as_namespace * ns,
94 geo_region_t region,
95 int maxnumcells,
96 uint64_t * cellctrp,
97 uint64_t * cellminp,
98 uint64_t * cellmaxp,
99 int * numcellsp)
100{
101 try
102 {
103 S2Region * regionp = (S2Region *) region;
104
105 S2RegionCoverer coverer;
106 if (ns) {
107 coverer.set_min_level(ns->geo2dsphere_within_min_level);
108 coverer.set_max_level(ns->geo2dsphere_within_max_level);
109 coverer.set_max_cells(ns->geo2dsphere_within_max_cells);
110 coverer.set_level_mod(ns->geo2dsphere_within_level_mod);
111 }
112 else {
113 // FIXME - we really don't want to hardcode these values, but
114 // some callers can't provide the namespace context ...
115 coverer.set_min_level(1);
116 coverer.set_max_level(30);
117 coverer.set_max_cells(12);
118 coverer.set_level_mod(1);
119 }
120 vector<S2CellId> covering;
121 coverer.GetCovering(*regionp, &covering);
122
123 // The coverer can always return 6 cells, even when max cells is
124 // less (regions which intersect all cube faces). If we get more
125 // then we asked for and it's greater then 6 something is wrong.
126 if (covering.size() > max(size_t(6), size_t(coverer.max_cells()))) {
127 return false;
128 }
129
130 for (size_t ii = 0; ii < covering.size(); ++ii)
131 {
132 if (ii == (size_t) maxnumcells)
133 {
134 cf_warning(AS_GEO, (char *) "region covered with %zu cells, "
135 "only %d allowed", covering.size(), maxnumcells);
136 return false;
137 }
138
139 if (cellctrp) {
140 cellctrp[ii] = covering[ii].id();
141 }
142 if (cellminp) {
143 cellminp[ii] = covering[ii].range_min().id();
144 }
145 if (cellmaxp) {
146 cellmaxp[ii] = covering[ii].range_max().id();
147 }
148
149 if (cellctrp) {
150 cf_detail(AS_GEO, (char *) "cell[%zu]: 0x%lx",
151 ii, cellctrp[ii]);
152 }
153
154 if (cellminp && cellmaxp) {
155 cf_detail(AS_GEO, (char *) "cell[%zu]: [0x%lx, 0x%lx]",
156 ii, cellminp[ii], cellmaxp[ii]);
157 }
158 }
159
160 *numcellsp = covering.size();
161 return true;
162 }
163 catch (exception const & ex)
164 {
165 cf_warning(AS_GEO, (char *) "geo_region_cover failed: %s", ex.what());
166 return false;
167 }
168}
169
170bool
171geo_point_centers(as_namespace * ns,
172 uint64_t cellidval,
173 int maxnumcenters,
174 uint64_t * center,
175 int * numcentersp)
176{
177 try
178 {
179 S2CellId incellid(cellidval);
180
181 *numcentersp = 0;
182
183 for (S2CellId cellid = incellid;
184 cellid.level() > 0;
185 cellid = cellid.parent())
186 {
187 // Make sure we don't overwrite the output array.
188 if (*numcentersp == maxnumcenters) {
189 break;
190 }
191 center[*numcentersp] = cellid.id();
192 *numcentersp += 1;
193 }
194 return true;
195 }
196 catch (exception const & ex)
197 {
198 cf_warning(AS_GEO, (char *) "geo_point_centers failed: %s", ex.what());
199 return false;
200 }
201}
202
203bool
204geo_point_within(uint64_t cellidval, geo_region_t region)
205{
206 try
207 {
208 S2Region * regionp = (S2Region *) region;
209 S2CellId cellid(cellidval);
210 bool iswithin = regionp->VirtualContainsPoint(cellid.ToPoint());
211 return iswithin;
212 }
213 catch (exception const & ex)
214 {
215 cf_warning(AS_GEO, (char *) "exception in geo_point_within: %s",
216 ex.what());
217 return false;
218 }
219}
220
221void
222geo_region_destroy(geo_region_t region)
223{
224 S2Region * regionp = (S2Region *) region;
225 if (regionp) {
226 delete regionp;
227 }
228}
229