You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

248 lines
8.4 KiB

import React, { useEffect, useState, useContext } from 'react';
import NodeEnvironment from 'jest-environment-node';
import {
add_geo_area_to_store, get_geo_area_from_store,
initialize_image_index, image_in_area
} from './geo_store.js';
import * as turf from '@turf/turf'
import KDBush from 'kdbush';
export async function sqljs_async_queries(sqljs_object, queries) {
//var t0 = performance.now();
for (let i = 0; i < (queries.length - 1); i++) {
//console.log("Query: ", queries[i]);
try {
sqljs_object.exec(queries[i]);
} catch (e) {
throw e;
}
}
//console.log("Query: ", queries[queries.length - 1]);
try {
var r = sqljs_object.exec(queries[queries.length - 1]);
} catch (e) {
throw e;
}
//console.log("Queries took ", (performance.now() - t0), " ms.");
//console.log("Query result for ", queries[queries.length - 1], ": ", r);
return r;
}
function fetch_sqljs_db_from_sqlite(filename) {
return new Promise(function (resolve, reject) {
var initSqlJs = require('sql.js');
initSqlJs({ locateFile: filename => process.env.PUBLIC_URL + `/sql.js/dist/${filename}` })
.then(SQL => {
fetch(filename)
.then(res => {
if (!res.ok) {
throw new Error(res.status);
}
return res.arrayBuffer();
})
.then(data => {
var array = new Uint8Array(data);
var sqljs_db = new SQL.Database(array);
resolve(sqljs_db);
})
});
});
}
export function regexp_match(string, regex) {
return string.match(regex) != null;
}
export function is_in_geo_polygon_from_store(image_id, lat, long, polygon_hash) {
return image_in_area(image_id, polygon_hash);
}
// Digikam stores its tree of tags as individual tags,
// linked only by their parent ID. This makes searching
// difficult. Therefore we add a column in the tag table
// which holds the "full tag" (e.g. People/John for the tag John).
export async function add_full_tag_info(db) {
var res = db.exec("SELECT id, pid, name FROM Tags LEFT JOIN TagProperties ON Tags.id=TagProperties.tagid");
if (!Array.isArray(res) || res.length == 0) {
throw new Error("Couldn't get tags information.");
}
var cols = res[0].columns;
var all_tags = res[0].values;
var full_tags = []; // id, full name pairs
const get_tag_by_id = (id) => {
for (let i = 0; i < all_tags.length; i++) {
if (all_tags[i][cols.indexOf("id")] == id) {
return all_tags[i];
}
}
return null;
};
// Solve the full paths for each tag.
all_tags.forEach(row => {
var name = row[cols.indexOf("name")];
var full = name;
var pid = row[cols.indexOf("pid")];
var id = row[cols.indexOf("id")];
var parent = pid == 0 ? null : get_tag_by_id(pid);
while (parent != null) {
full = parent[cols.indexOf("name")] + "/" + full;
pid = parent[cols.indexOf("pid")];
parent = pid == 0 ? null : get_tag_by_id(pid);
}
var new_entry = { id: id, fullname: full };
full_tags.push(new_entry);
});
// Now put them in the database.
db.exec("ALTER TABLE Tags ADD fullname TEXT;");
full_tags.forEach(tag => {
var query = 'UPDATE Tags SET fullname="' + tag.fullname + '" WHERE id=' + tag.id + ';';
db.exec(query);
});
return db;
}
function polygons_benchmark(database) {
var img_query = "SELECT GROUP_CONCAT(Images.id), ImagePositions.latitudeNumber, ImagePositions.longitudeNumber FROM Images "
+ "LEFT JOIN ImagePositions ON ImagePositions.imageid=Images.id WHERE ImagePositions.latitudeNumber NOT NULL GROUP BY ImagePositions.latitudeNumber, ImagePositions.longitudeNumber;";
sqljs_async_queries(database, [img_query]).then(res => {
console.log("Images query result:", res);
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 polies;
var points = []; // Contains all unique geometric points as [lat long]
var ids_per_point = []; // Contains a list of ids for each unique geometric point
console.log("Nominatim geo answer:", jsonres);
if (Array.isArray(jsonres) && jsonres.length > 0) {
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([parseFloat(row[cols.indexOf("longitudeNumber")]), parseFloat(row[cols.indexOf("latitudeNumber")])]);
ids_per_point.push(row[cols.indexOf("GROUP_CONCAT(Images.id)")].split(',').map(e => parseInt(e)));
});
}
console.log("Points: ", points);
console.log("IDs per point: ", ids_per_point);
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);
}
{
// 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");
console.time("Total KDBush search time");
// 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 = [];
hits.forEach(hit => {
const point = points[hit];
const is_real_hit = turf.booleanPointInPolygon(turf.point(points[hit]), polies);
if (is_real_hit) {
realhits.push(hit);
}
});
console.log("Real: ", realhits);
console.timeEnd("Get exact hits after KDBush");
// Expand to all image IDs
console.time("Expand exact hits to IDs");
var hit_ids = [];
realhits.forEach(idx => {
hit_ids = hit_ids.concat(ids_per_point[idx]);
});
console.log("Real hit IDs: ", hit_ids);
console.timeEnd("Expand exact hits to IDs");
console.timeEnd("Total KDBush search time");
}
});
})
.catch(err => { throw err; });
}
export function ProvideDB(props) {
const { children, db_url } = props;
const [db, setDb] = useState(null);
const [error, setError] = useState(false);
useEffect(() => {
fetch_sqljs_db_from_sqlite(db_url)
.then(db => {
add_full_tag_info(db)
.then((newdb) => {
initialize_image_index(newdb).then(() => {
db.create_function("REGEXP", regexp_match);
db.create_function("IS_IN_GEO", is_in_geo_polygon_from_store);
//polygons_benchmark(db);
setError(false);
setDb(newdb);
})
})
})
.catch(error => { setError(error); });
}, [])
var child_props = {
db: db,
db_error: error,
};
return children({ ...child_props });
}