diff --git a/package.json b/package.json index 1cc4cbb..41b56f2 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "eslint": "^6.8.0", "eslint-plugin-react-hooks": "^2.3.0", "geojson-utils": "^1.1.0", + "kdbush": "^3.0.0", "leaflet": "^1.6.0", "lodash": "^4.17.15", "object-hash": "^2.0.1", diff --git a/src/database.js b/src/database.js index aeacb5b..b42f5c3 100644 --- a/src/database.js +++ b/src/database.js @@ -2,8 +2,9 @@ import React, { useEffect, useState, useContext } from 'react'; import NodeEnvironment from 'jest-environment-node'; import { add_geo_area_to_store, get_geo_area_from_store } from './geo_store.js'; +import * as turf from '@turf/turf' -import pointsWithinPolygon from '@turf/points-within-polygon'; +import KDBush from 'kdbush'; export async function sqljs_async_queries(sqljs_object, queries) { //var t0 = performance.now(); @@ -109,37 +110,96 @@ export async function add_full_tag_info(db) { function polygons_benchmark(database) { var img_query = "SELECT Images.id, ImagePositions.latitudeNumber, ImagePositions.longitudeNumber FROM Images " - + "LEFT JOIN ImagePositions ON ImagePositions.imageid=Images.id GROUP BY Images.id;"; + + "LEFT JOIN ImagePositions ON ImagePositions.imageid=Images.id WHERE ImagePositions.latitudeNumber NOT NULL GROUP BY Images.id;"; sqljs_async_queries(database, [img_query]).then(res => { - fetch("https://nominatim.openstreetmap.org/search?polygon_geojson=1&polygon_threshold=0.001&format=json&limit=5&q=Australia") + fetch("https://nominatim.openstreetmap.org/search?polygon_geojson=1&polygon_threshold=0.001&format=json&limit=1&q=Australia") .then(res => res.json()) .then(jsonres => { - var geojson; + var polies; var points = []; console.log("Nominatim geo answer:", jsonres); if (Array.isArray(jsonres) && jsonres.length > 0) { - geojson = jsonres[0].geojson; + polies = jsonres[0].geojson; } if (res && Array.isArray(res) && res.length > 0) { var cols = res[0].columns; var data = res[0].values; data.forEach(row => { - points.push([row[cols.indexOf("longitudeNumber")], row[cols.indexOf("latitudeNumber")]]); + points.push([parseFloat(row[cols.indexOf("longitudeNumber")]), parseFloat(row[cols.indexOf("latitudeNumber")])]); }); } console.log("Points: ", points); - console.log("GEOJSON: ", geojson); - - console.time("points within polygon"); - var found = pointsWithinPolygon(points, geojson); - console.timeEnd("points within polygon"); + console.log("Nominatim GEOJSON: ", polies); + + { + // Try Turf + const tpoints = turf.points(points); + console.time("Turf points within polygon"); + var found = turf.pointsWithinPolygon(tpoints, polies); + console.timeEnd("Turf points within polygon"); + console.log("Turf PointsWithinPolygon: ", found); + } - console.log("PointsWithinPolygon: ", found); + { + // Try KDBush + console.time("Build KDBush index"); + const index = new KDBush(points, p => p[0], p => p[1], 16, Float64Array); + console.timeEnd("Build KDBush index"); + + // Get bounding boxes for all subpolygons + var boxes = []; + console.time("Build KDBush poly boxes"); + for (let i = 0; i < polies.coordinates.length; i++) { + const outerPoly = polies.coordinates[i][0]; + var minx = Number.POSITIVE_INFINITY; + var miny = Number.POSITIVE_INFINITY; + var maxx = Number.NEGATIVE_INFINITY; + var maxy = Number.NEGATIVE_INFINITY; + for (let j = 0; j < outerPoly.length; j++) { + minx = Math.min(minx, outerPoly[j][0]); + miny = Math.min(miny, outerPoly[j][1]); + maxx = Math.max(maxx, outerPoly[j][0]); + maxy = Math.max(maxy, outerPoly[j][1]); + } + boxes.push([minx, miny, maxx, maxy]); + } + console.timeEnd("Build KDBush poly boxes"); + console.log("KDBush boxes: ", boxes); + + // Test points in KD tree against each subpolygon bounding box + console.time("Find box points in KDBush"); + let hits = new Set(); + for (let i = 0; i < boxes.length; i++) { + const ids = index.range(boxes[i][0], boxes[i][1], boxes[i][2], boxes[i][3]); + ids.forEach(e => hits.add(e)); + } + console.timeEnd("Find box points in KDBush"); + console.log("Hits: ", hits); + + // Test hit points exactly + console.time("Get exact hits after KDBush"); + var realhits = []; + var hitcache = {}; + hits.forEach(hit => { + const point = points[hit]; + if(point in hitcache && hitcache[point]) { + realhits.push(hit); + return; + } + const is_real_hit = turf.booleanPointInPolygon(turf.point(points[hit]), polies); + hitcache[point] = is_real_hit; + if(is_real_hit){ + realhits.push(hit); + } + }); + console.log("Real: ", realhits); + console.timeEnd("Get exact hits after KDBush"); + } }); }) .catch(err => { throw err; }); diff --git a/yarn.lock b/yarn.lock index 7fe7f21..55c7275 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6505,6 +6505,11 @@ jsx-ast-utils@^2.2.1: array-includes "^3.0.3" object.assign "^4.1.0" +kdbush@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/kdbush/-/kdbush-3.0.0.tgz#f8484794d47004cc2d85ed3a79353dbe0abc2bf0" + integrity sha512-hRkd6/XW4HTsA9vjVpY9tuXJYLSlelnkTmVFu4M9/7MIYQtFcHpbugAU7UbOfjOiVSVYl2fqgBuJ32JUmRo5Ew== + killable@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/killable/-/killable-1.0.1.tgz#4c8ce441187a061c7474fb87ca08e2a638194892"