improve geo stuff with a name.

master
Sander Vocke 6 years ago
parent e34eb69c00
commit e2ad3b825c
  1. 8
      src/database.js
  2. 22
      src/geo_store.js
  3. 17
      src/map.js
  4. 22
      src/polygons.js
  5. 14
      src/queries.js
  6. 7
      src/resultsview.js
  7. 32
      src/userquerywidget.js

@ -1,7 +1,7 @@
import React, { useEffect, useState, useContext } from 'react'; import React, { useEffect, useState, useContext } from 'react';
import NodeEnvironment from 'jest-environment-node'; import NodeEnvironment from 'jest-environment-node';
import { add_polygon_to_store, get_polygon_from_store } from './polygons.js'; import { add_geo_area_to_store, get_geo_area_from_store } from './geo_store.js';
export async function sqljs_async_queries(sqljs_object, queries) { export async function sqljs_async_queries(sqljs_object, queries) {
//var t0 = performance.now(); //var t0 = performance.now();
@ -50,10 +50,10 @@ export function regexp_match(string, regex) {
} }
export function is_in_geo_polygon_from_store(lat, long, polygon_hash) { export function is_in_geo_polygon_from_store(lat, long, polygon_hash) {
const geojson = get_polygon_from_store(polygon_hash); const area = get_geo_area_from_store(polygon_hash);
var gju = require('geojson-utils'); var gju = require('geojson-utils');
return gju.pointInPolygon({ "type": "Point", "coordinates": [long, lat] }, return gju.pointInPolygon({ "type": "Point", "coordinates": [long, lat] },
geojson); area.geojson);
} }
// Digikam stores its tree of tags as individual tags, // Digikam stores its tree of tags as individual tags,
@ -115,7 +115,7 @@ export function ProvideDB(props) {
.then(db => { .then(db => {
add_full_tag_info(db).then((newdb) => { add_full_tag_info(db).then((newdb) => {
db.create_function("REGEXP", regexp_match); db.create_function("REGEXP", regexp_match);
db.create_function("IS_IN_GEO_POLYGON", is_in_geo_polygon_from_store); db.create_function("IS_IN_GEO", is_in_geo_polygon_from_store);
setError(false); setError(false);
setDb(newdb); setDb(newdb);
}) })

@ -0,0 +1,22 @@
var g_GeoStore = {};
export function hash_geo_area(geo_area) {
var hash = require('object-hash');
return hash(geo_area);
}
export function add_geo_area_to_store(area) {
var h = hash_geo_area(area);
if(!(h in g_GeoStore)) {
g_GeoStore[h] = area;
}
return h;
}
export function get_geo_area_from_store(hash) {
if(hash in g_GeoStore) {
return g_GeoStore[hash];
}
throw new Error("Requested non-existent geo area from store.");
}

@ -3,8 +3,6 @@ import React, { useEffect, useState } from 'react';
import { makeStyles } from '@material-ui/core/styles'; import { makeStyles } from '@material-ui/core/styles';
import Box from '@material-ui/core/Box'; import Box from '@material-ui/core/Box';
import { SearchBar } from './searchbar.js';
import "leaflet/dist/leaflet.css" import "leaflet/dist/leaflet.css"
import L from 'leaflet'; import L from 'leaflet';
@ -14,7 +12,7 @@ export function MapView(props) {
// Normally we'd make such an object in the parent and pass it as a prop. However, the object cannot // Normally we'd make such an object in the parent and pass it as a prop. However, the object cannot
// be created without a div being present for it to reside in, so we have a chicken-egg problem. // be created without a div being present for it to reside in, so we have a chicken-egg problem.
// We solve it by loading the map after mounting this component, then passing a handle to the parent. // We solve it by loading the map after mounting this component, then passing a handle to the parent.
const { onMapChange } = props; const { onMapChange, style } = props;
const [map, setMap] = useState(null); const [map, setMap] = useState(null);
const [camera, setCameraNoPush] = useState([51.505, -0.09, 13]); //lat, long, zoom const [camera, setCameraNoPush] = useState([51.505, -0.09, 13]); //lat, long, zoom
@ -60,16 +58,5 @@ export function MapView(props) {
} }
}, [map]) }, [map])
const style = { return <div id={id} style={style}></div>
width: "100%",
height: "600px",
};
return (
<>
<Box>
<div id={id} style={style}></div>
</Box>
</>
);
} }

@ -1,22 +0,0 @@
var g_PolygonStore = {};
export function hash_polygon(polygon) {
var hash = require('object-hash');
return hash(polygon);
}
export function add_polygon_to_store(polygon) {
var h = hash_polygon(polygon);
if(!(h in g_PolygonStore)) {
g_PolygonStore[h] = polygon;
}
return h;
}
export function get_polygon_from_store(polygon_hash) {
if(polygon_hash in g_PolygonStore) {
return g_PolygonStore[polygon_hash];
}
throw new Error("Requested non-existent polygon from store.");
}

@ -2,7 +2,7 @@
import { create_photo, create_album, create_tag } from './media.js'; import { create_photo, create_album, create_tag } from './media.js';
import { sqljs_async_queries } from './database.js'; import { sqljs_async_queries } from './database.js';
import { format } from 'date-fns'; import { format } from 'date-fns';
import { add_polygon_to_store } from './polygons.js'; import { add_geo_area_to_store } from './geo_store.js';
export function escape_regex(s) { export function escape_regex(s) {
return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
@ -303,10 +303,12 @@ export class LogicalOperatorFilter extends ResultFilter {
export class LocationFilter extends ResultFilter { export class LocationFilter extends ResultFilter {
// The location filter always compares object locations (points) // The location filter always compares object locations (points)
// to a location polygon and filters objects outside said polygon. // to a location and filters objects outside said location.
constructor(rtype, polygon) { // Locations are directly stored from Nominatim API responses,
// which have GeoJSON and other metadata.
constructor(rtype, geo_area) {
super(rtype); super(rtype);
this.polygon = polygon; this.geo_area = geo_area;
} }
to_sql_where() { to_sql_where() {
@ -316,9 +318,9 @@ export class LocationFilter extends ResultFilter {
// by the custom comparison function invoked by SQL.js. // by the custom comparison function invoked by SQL.js.
// We need to store our polygon in said state and then return the query to guarantee that // We need to store our polygon in said state and then return the query to guarantee that
// it will have access to said polygon. // it will have access to said polygon.
const hash = add_polygon_to_store(this.polygon); const hash = add_geo_area_to_store(this.geo_area);
return '(ImagePositions.latitudeNumber NOT NULL AND ImagePositions.longitudeNumber NOT NULL AND ' return '(ImagePositions.latitudeNumber NOT NULL AND ImagePositions.longitudeNumber NOT NULL AND '
+ 'IS_IN_GEO_POLYGON(ImagePositions.latitudeNumber, ImagePositions.longitudeNumber, "' + hash + '"))'; + 'IS_IN_GEO(ImagePositions.latitudeNumber, ImagePositions.longitudeNumber, "' + hash + '"))';
} }
simplify() { return this; } simplify() { return this; }

@ -121,6 +121,11 @@ export function ResultsView(props) {
setActiveIndex(newindex); setActiveIndex(newindex);
} }
const map_style = {
width: "100%",
height: "500px",
};
return ( return (
<div className={classes.root}> <div className={classes.root}>
<AppBar position="static"> <AppBar position="static">
@ -148,7 +153,7 @@ export function ResultsView(props) {
</TabPanel> </TabPanel>
<TabPanel activeIndex={activeIndex} myIndex={3}> <TabPanel activeIndex={activeIndex} myIndex={3}>
<Box className={classes.root}> <Box className={classes.root}>
<MapView></MapView> <MapView style={map_style}></MapView>
</Box> </Box>
</TabPanel> </TabPanel>
</div> </div>

@ -58,7 +58,7 @@ const useStyles = makeStyles(theme => ({
margin: "6px", margin: "6px",
}, },
mapcontainer: { mapcontainer: {
width: "600px", width: "300px",
}, },
})); }));
@ -72,25 +72,17 @@ export function EditLocationFilterExpression(props) {
const _ = require('lodash'); const _ = require('lodash');
function updateProposal(result) { function updateProposal(result) {
geoLayers.forEach(layer=> { // Show the geometry proposed on the map.
geoLayers.forEach(layer => {
map.removeLayer(layer); map.removeLayer(layer);
}); });
var layer = L.geoJSON(result.geojson).addTo(map); var layer = L.geoJSON(result.geojson).addTo(map);
map.flyToBounds(layer.getBounds()); map.fitBounds(layer.getBounds());
setGeoLayers([layer]); setGeoLayers([layer]);
// // Show the polyline on the map
// polyLayers.forEach(layer => {
// map.removeLayer(layer);
// });
// var polyline = L.polyline(polygon, { color: 'blue' }).addTo(map);
// map.flyToBounds(polyline.getBounds());
// setPolyLayers([polyline]);
// Update the proposed polygon // Update the proposed polygon
console.log("Updated proposal:", result); console.log("Updated proposal:", result);
// result.geojson.coordinates[0].map(longlat => [longlat[1], longlat[0]]); setProposal(result);
setProposal(result.geojson);
} }
function onSearch(query) { function onSearch(query) {
@ -101,7 +93,6 @@ export function EditLocationFilterExpression(props) {
fetch("https://nominatim.openstreetmap.org/search?polygon_geojson=1&polygon_threshold=0.001&format=json&limit=5&q=" + query) fetch("https://nominatim.openstreetmap.org/search?polygon_geojson=1&polygon_threshold=0.001&format=json&limit=5&q=" + query)
.then(res => res.json()) .then(res => res.json())
.then(jsonres => { .then(jsonres => {
//console.log("Nominatim result: ", jsonres);
if (Array.isArray(jsonres) && jsonres.length > 0) { if (Array.isArray(jsonres) && jsonres.length > 0) {
updateProposal(jsonres[0]); updateProposal(jsonres[0]);
} }
@ -110,10 +101,15 @@ export function EditLocationFilterExpression(props) {
function handleUseProposal() { function handleUseProposal() {
var new_filter = _.cloneDeep(filter); var new_filter = _.cloneDeep(filter);
new_filter.polygon = proposal; new_filter.geo_area = proposal;
onChange(new_filter); onChange(new_filter);
} }
const map_style = {
width: "300px",
height: "300px",
}
// This component is a bit tricky. MapView is not very React-y because it manages its // This component is a bit tricky. MapView is not very React-y because it manages its
// own state inside (this is due to how Leaflet is integrated). // own state inside (this is due to how Leaflet is integrated).
// We don't want to render a new MapView whenever our filter changes. // We don't want to render a new MapView whenever our filter changes.
@ -124,11 +120,11 @@ export function EditLocationFilterExpression(props) {
<SearchBar onSubmit={onSearch} /> <SearchBar onSubmit={onSearch} />
</Box> </Box>
<Box className={classes.mapcontainer}> <Box className={classes.mapcontainer}>
<MapView onMapChange={setMap}></MapView> <MapView onMapChange={setMap} style={map_style}></MapView>
</Box> </Box>
<Box> <Box>
<Button variant="contained" onClick={handleUseProposal}>Use shown boundary</Button> <Button variant="contained" onClick={handleUseProposal}>Use shown area</Button>
<Typography>Polygon with {filter.polygon.length} points.</Typography> <Typography>Using: {filter.geo_area.display_name}</Typography>
</Box> </Box>
</> </>
); );

Loading…
Cancel
Save