2 * Copyright (c) 2018, Bertold Van den Bergh (vandenbergh@bertold.org)
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 * * Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * * Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 * * Neither the name of the author nor the
13 * names of its contributors may be used to endorse or promote products
14 * derived from this software without specific prior written permission.
16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR DISTRIBUTOR BE LIABLE FOR ANY
20 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35 #include "zonedetect.h"
37 struct ZoneDetectOpaque {
51 uint32_t metadataOffset;
55 static int32_t ZDFloatToFixedPoint(float input, float scale, unsigned int precision) {
56 float inputScaled = input / scale;
57 return inputScaled * (float)(1 << (precision-1));
60 static unsigned int ZDDecodeVariableLengthUnsigned(ZoneDetect* library, uint32_t* index, uint32_t* result) {
62 unsigned int i=0, shift = 0;
64 if(*index >= library->length) {
68 uint8_t* buffer = library->mapping + *index;
69 uint8_t* bufferEnd = library->mapping + library->length - 1;
72 value |= (buffer[i] & 0x7F) << shift;
75 if(!(buffer[i] & 0x80)) {
80 if(buffer + i > bufferEnd) {
91 static unsigned int ZDDecodeVariableLengthSigned(ZoneDetect* library, uint32_t* index, int32_t* result) {
93 unsigned int retVal = ZDDecodeVariableLengthUnsigned(library, index, &value);
94 *result = (value & 1)?-(value/2):(value/2);
98 static char* ZDParseString(ZoneDetect* library, uint32_t* index) {
100 if(!ZDDecodeVariableLengthUnsigned(library, index, &strLength)) {
104 uint32_t strOffset = *index;
105 unsigned int remoteStr = 0;
106 if(strLength >= 256) {
107 strOffset = library->metadataOffset + strLength - 256;
110 if(!ZDDecodeVariableLengthUnsigned(library, &strOffset, &strLength)) {
114 if(strLength > 256) {
119 char* str = malloc(strLength + 1);
123 for(i=0; i<strLength; i++) {
124 str[i] = library->mapping[strOffset+i] ^ 0x80;
136 static int ZDParseHeader(ZoneDetect* library) {
137 if(library->length < 7) {
141 if(memcmp(library->mapping, "PLB", 3)) {
145 library->tableType = library->mapping[3];
146 library->version = library->mapping[4];
147 library->precision = library->mapping[5];
148 library->numFields = library->mapping[6];
150 if(library->version != 0) {
156 library->fieldNames = malloc(library->numFields * sizeof(char*));
158 for(i=0; i<library->numFields; i++) {
159 library->fieldNames[i] = ZDParseString(library, &index);
162 library->notice = ZDParseString(library, &index);
163 if(!library->notice) {
168 /* Read section sizes */
169 /* By memset: library->bboxOffset = 0 */
171 if(!ZDDecodeVariableLengthUnsigned(library, &index, &tmp)) return -1;
172 library->metadataOffset = tmp + library->bboxOffset;
174 if(!ZDDecodeVariableLengthUnsigned(library, &index, &tmp))return -1;
175 library->dataOffset = tmp + library->metadataOffset;
177 if(!ZDDecodeVariableLengthUnsigned(library, &index, &tmp)) return -1;
179 /* Add header size to everything */
180 library->bboxOffset += index;
181 library->metadataOffset += index;
182 library->dataOffset += index;
184 /* Verify file length */
185 if(tmp + library->dataOffset != library->length) {
192 static int ZDPointInBox(int32_t xl, int32_t x, int32_t xr, int32_t yl, int32_t y, int32_t yr) {
193 if((xl <= x && x <= xr) || (xr <= x && x <= xl)) {
194 if((yl <= y && y <= yr) || (yr <= y && y <= yl)) {
202 static ZDLookupResult ZDPointInPolygon(ZoneDetect* library, uint32_t polygonIndex, int32_t latFixedPoint, int32_t lonFixedPoint, uint64_t* distanceSqrMin) {
203 uint32_t numVertices;
204 int32_t pointLat = 0, pointLon = 0, diffLat = 0, diffLon = 0, firstLat = 0, firstLon = 0, prevLat = 0, prevLon = 0;
207 /* Read number of vertices */
208 if(!ZDDecodeVariableLengthUnsigned(library, &polygonIndex, &numVertices)) return ZD_LOOKUP_PARSE_ERROR;
209 if(numVertices > 1000000) return ZD_LOOKUP_PARSE_ERROR;
211 int prevQuadrant = 0, winding = 0;
214 for(i=0; i<=numVertices; i++) {
216 if(!ZDDecodeVariableLengthSigned(library, &polygonIndex, &diffLat)) return ZD_LOOKUP_PARSE_ERROR;
217 if(!ZDDecodeVariableLengthSigned(library, &polygonIndex, &diffLon)) return ZD_LOOKUP_PARSE_ERROR;
225 /* The polygons should be closed, but just in case */
230 /* Check if point is ON the border */
231 if(pointLat == latFixedPoint && pointLon == lonFixedPoint) {
232 if(distanceSqrMin) *distanceSqrMin=0;
233 return ZD_LOOKUP_ON_BORDER_VERTEX;
238 if(pointLat>=latFixedPoint) {
239 if(pointLon>=lonFixedPoint) {
245 if(pointLon>=lonFixedPoint) {
253 int windingNeedCompare = 0, lineIsStraight = 0;
256 /* Calculate winding number */
257 if(quadrant == prevQuadrant) {
259 } else if(quadrant == (prevQuadrant + 1) % 4) {
261 } else if((quadrant + 1) % 4 == prevQuadrant) {
264 windingNeedCompare = 1;
267 /* Avoid horizontal and vertical lines */
268 if((pointLon == prevLon || pointLat == prevLat)) {
272 /* Calculate the parameters of y=ax+b if needed */
273 if(!lineIsStraight && (distanceSqrMin || windingNeedCompare)) {
274 a = ((float)pointLat - (float)prevLat)/((float)pointLon - prevLon);
275 b = (float)pointLat - a*(float)pointLon;
278 /* Jumped two quadrants. */
279 if(windingNeedCompare) {
281 if(distanceSqrMin) *distanceSqrMin=0;
282 return ZD_LOOKUP_ON_BORDER_SEGMENT;
285 /* Check if the target is on the border */
286 int32_t intersectLon = ((float)latFixedPoint - b)/a;
287 if(intersectLon == lonFixedPoint) {
288 if(distanceSqrMin) *distanceSqrMin=0;
289 return ZD_LOOKUP_ON_BORDER_SEGMENT;
292 /* Ok, it's not. In which direction did we go round the target? */
293 int sign = (intersectLon < lonFixedPoint)?2:-2;
294 if(quadrant == 2 || quadrant == 3) {
301 /* Calculate closest point on line (if needed) */
303 float closestLon, closestLat;
304 if(!lineIsStraight) {
305 closestLon=((float)lonFixedPoint+a*(float)latFixedPoint-a*b)/(a*a+1);
306 closestLat=(a*((float)lonFixedPoint+a*(float)latFixedPoint)+b)/(a*a+1);
308 if(pointLon == prevLon) {
310 closestLat=latFixedPoint;
312 closestLon=lonFixedPoint;
317 int closestInBox = ZDPointInBox(pointLon, closestLon, prevLon, pointLat, closestLat, prevLat);
319 int64_t diffLat, diffLon;
321 /* Calculate squared distance to segment. */
322 diffLat = closestLat - latFixedPoint;
323 diffLon = (closestLon - lonFixedPoint);
326 * Calculate squared distance to vertices
327 * It is enough to check the current point since the polygon is closed.
329 diffLat = pointLat - latFixedPoint;
330 diffLon = (pointLon - lonFixedPoint);
333 /* Note: lon has half scale */
334 uint64_t distanceSqr = diffLat*diffLat + diffLon*diffLon*4;
335 if(distanceSqr < *distanceSqrMin) *distanceSqrMin = distanceSqr;
339 prevQuadrant = quadrant;
345 return ZD_LOOKUP_IN_ZONE;
346 } else if(winding == 4) {
347 return ZD_LOOKUP_IN_EXCLUDED_ZONE;
348 } else if(winding == 0) {
349 return ZD_LOOKUP_NOT_IN_ZONE;
352 /* Should not happen */
353 if(distanceSqrMin) *distanceSqrMin=0;
354 return ZD_LOOKUP_ON_BORDER_SEGMENT;
357 void ZDCloseDatabase(ZoneDetect* library) {
359 if(library->fieldNames) {
361 for(i=0; i<library->numFields; i++) {
362 if(library->fieldNames[i]) {
363 free(library->fieldNames[i]);
366 free(library->fieldNames);
368 if(library->notice) {
369 free(library->notice);
371 if(library->mapping) {
372 munmap(library->mapping, library->length);
374 if(library->fd >= 0) {
381 ZoneDetect* ZDOpenDatabase(const char* path) {
382 ZoneDetect* library = (ZoneDetect*)malloc(sizeof(*library));
385 memset(library, 0, sizeof(*library));
387 library->fd = open(path, O_RDONLY | O_CLOEXEC);
388 if(library->fd < 0) {
392 library->length = lseek(library->fd, 0, SEEK_END);
393 if(library->length <= 0) {
396 lseek(library->fd, 0, SEEK_SET);
398 library->mapping = mmap(NULL, library->length, PROT_READ, MAP_PRIVATE | MAP_FILE, library->fd, 0);
399 if(!library->mapping) {
403 /* Parse the header */
404 if(ZDParseHeader(library)) {
412 ZDCloseDatabase(library);
416 ZoneDetectResult* ZDLookup(ZoneDetect* library, float lat, float lon, float* safezone) {
417 int32_t latFixedPoint = ZDFloatToFixedPoint(lat, 90, library->precision);
418 int32_t lonFixedPoint = ZDFloatToFixedPoint(lon, 180, library->precision);
419 unsigned int numResults = 0;
420 uint64_t distanceSqrMin=-1;
422 /* Iterate over all polygons */
423 uint32_t bboxIndex = library->bboxOffset;
424 int32_t metadataIndex = 0;
425 int32_t polygonIndex = 0;
427 ZoneDetectResult* results = malloc(sizeof(ZoneDetectResult));
432 while(bboxIndex < library->metadataOffset) {
433 int32_t minLat, minLon, maxLat, maxLon, metadataIndexDelta;
434 uint32_t polygonIndexDelta;
435 if(!ZDDecodeVariableLengthSigned(library, &bboxIndex, &minLat)) break;
436 if(!ZDDecodeVariableLengthSigned(library, &bboxIndex, &minLon)) break;
437 if(!ZDDecodeVariableLengthSigned(library, &bboxIndex, &maxLat)) break;
438 if(!ZDDecodeVariableLengthSigned(library, &bboxIndex, &maxLon)) break;
439 if(!ZDDecodeVariableLengthSigned(library, &bboxIndex, &metadataIndexDelta)) break;
440 if(!ZDDecodeVariableLengthUnsigned(library, &bboxIndex, &polygonIndexDelta)) break;
442 metadataIndex+=metadataIndexDelta;
443 polygonIndex+=polygonIndexDelta;
445 if(latFixedPoint >= minLat) {
446 if(latFixedPoint <= maxLat &&
447 lonFixedPoint >= minLon &&
448 lonFixedPoint <= maxLon) {
451 if(library->metadataOffset + metadataIndex >= library->dataOffset) continue;
452 if(library->dataOffset + polygonIndex >= library->length) continue;
454 ZDLookupResult lookupResult = ZDPointInPolygon(library, library->dataOffset + polygonIndex, latFixedPoint, lonFixedPoint, (safezone)?&distanceSqrMin:NULL);
455 if(lookupResult == ZD_LOOKUP_PARSE_ERROR) {
457 } else if(lookupResult != ZD_LOOKUP_NOT_IN_ZONE) {
458 ZoneDetectResult* newResults = realloc(results, sizeof(ZoneDetectResult) * (numResults+2));
461 results = newResults;
462 results[numResults].metaId = metadataIndex;
463 results[numResults].numFields = library->numFields;
464 results[numResults].fieldNames = library->fieldNames;
465 results[numResults].lookupResult = lookupResult;
474 /* The data is sorted along minLat */
479 /* Clean up results */
481 for(i=0; i<numResults; i++) {
483 ZDLookupResult overrideResult = ZD_LOOKUP_IGNORE;
484 for(j=i; j<numResults; j++) {
485 if(results[i].metaId == results[j].metaId) {
486 ZDLookupResult tmpResult = results[j].lookupResult;
487 results[j].lookupResult = ZD_LOOKUP_IGNORE;
489 /* This is the same result. Is it an exclusion zone? */
490 if(tmpResult == ZD_LOOKUP_IN_ZONE) {
492 } else if(tmpResult == ZD_LOOKUP_IN_EXCLUDED_ZONE) {
495 /* If on the bodrder then the final result is on the border */
496 overrideResult = tmpResult;
502 if(overrideResult != ZD_LOOKUP_IGNORE) {
503 results[i].lookupResult = overrideResult;
506 results[i].lookupResult = ZD_LOOKUP_IN_ZONE;
511 /* Remove zones to ignore */
512 unsigned int newNumResults = 0;
513 for(i=0; i<numResults; i++) {
514 if(results[i].lookupResult != ZD_LOOKUP_IGNORE) {
515 results[newNumResults] = results[i];
519 numResults = newNumResults;
521 /* Lookup metadata */
522 for(i=0; i<numResults; i++) {
523 uint32_t tmpIndex = library->metadataOffset + results[i].metaId;
524 results[i].data = malloc(library->numFields * sizeof(char*));
525 if(results[i].data) {
526 for(j=0; j<library->numFields; j++) {
527 results[i].data[j] = ZDParseString(library, &tmpIndex);
532 /* Write end marker */
533 results[numResults].lookupResult = ZD_LOOKUP_END;
534 results[numResults].numFields = 0;
535 results[numResults].fieldNames = NULL;
536 results[numResults].data = NULL;
539 *safezone = sqrtf(distanceSqrMin) * 90 / (float)(1 << (library->precision-1));
545 void ZDFreeResults(ZoneDetectResult* results) {
546 unsigned int index = 0;
552 while(results[index].lookupResult != ZD_LOOKUP_END) {
553 if(results[index].data) {
555 for(i=0; i<results[index].numFields; i++) {
556 if(results[index].data[i]) {
557 free(results[index].data[i]);
560 free(results[index].data);
567 const char* ZDGetNotice(ZoneDetect* library) {
568 return library->notice;
571 uint8_t ZDGetTableType(ZoneDetect* library) {
572 return library->tableType;
575 const char* ZDLookupResultToString(ZDLookupResult result) {
577 case ZD_LOOKUP_IGNORE:
581 case ZD_LOOKUP_PARSE_ERROR:
582 return "Parsing error";
583 case ZD_LOOKUP_NOT_IN_ZONE:
584 return "Not in zone";
585 case ZD_LOOKUP_IN_ZONE:
587 case ZD_LOOKUP_IN_EXCLUDED_ZONE:
588 return "In excluded zone";
589 case ZD_LOOKUP_ON_BORDER_VERTEX:
590 return "Target point is border vertex";
591 case ZD_LOOKUP_ON_BORDER_SEGMENT:
592 return "Target point is on border";