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 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) {
//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) {
const geojson = get_polygon_from_store(polygon_hash);
const area = get_geo_area_from_store(polygon_hash);
var gju = require('geojson-utils');
return gju.pointInPolygon({ "type": "Point", "coordinates": [long, lat] },
geojson);
area.geojson);
}
// Digikam stores its tree of tags as individual tags,
@ -115,7 +115,7 @@ export function ProvideDB(props) {
.then(db => {
add_full_tag_info(db).then((newdb) => {
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);
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 Box from '@material-ui/core/Box';
import { SearchBar } from './searchbar.js';
import "leaflet/dist/leaflet.css"
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
// 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.
const { onMapChange } = props;
const { onMapChange, style } = props;
const [map, setMap] = useState(null);
const [camera, setCameraNoPush] = useState([51.505, -0.09, 13]); //lat, long, zoom
@ -60,16 +58,5 @@ export function MapView(props) {
}
}, [map])
const style = {
width: "100%",
height: "600px",
};
return (
<>
<Box>
<div id={id} style={style}></div>
</Box>
</>
);
return <div id={id} style={style}></div>
}

@ -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 { sqljs_async_queries } from './database.js';
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) {
return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
@ -303,10 +303,12 @@ export class LogicalOperatorFilter extends ResultFilter {
export class LocationFilter extends ResultFilter {
// The location filter always compares object locations (points)
// to a location polygon and filters objects outside said polygon.
constructor(rtype, polygon) {
// to a location and filters objects outside said location.
// Locations are directly stored from Nominatim API responses,
// which have GeoJSON and other metadata.
constructor(rtype, geo_area) {
super(rtype);
this.polygon = polygon;
this.geo_area = geo_area;
}
to_sql_where() {
@ -316,9 +318,9 @@ export class LocationFilter extends ResultFilter {
// 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
// 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 '
+ 'IS_IN_GEO_POLYGON(ImagePositions.latitudeNumber, ImagePositions.longitudeNumber, "' + hash + '"))';
+ 'IS_IN_GEO(ImagePositions.latitudeNumber, ImagePositions.longitudeNumber, "' + hash + '"))';
}
simplify() { return this; }

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

@ -58,7 +58,7 @@ const useStyles = makeStyles(theme => ({
margin: "6px",
},
mapcontainer: {
width: "600px",
width: "300px",
},
}));
@ -72,25 +72,17 @@ export function EditLocationFilterExpression(props) {
const _ = require('lodash');
function updateProposal(result) {
geoLayers.forEach(layer=> {
// Show the geometry proposed on the map.
geoLayers.forEach(layer => {
map.removeLayer(layer);
});
var layer = L.geoJSON(result.geojson).addTo(map);
map.flyToBounds(layer.getBounds());
map.fitBounds(layer.getBounds());
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
console.log("Updated proposal:", result);
// result.geojson.coordinates[0].map(longlat => [longlat[1], longlat[0]]);
setProposal(result.geojson);
setProposal(result);
}
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)
.then(res => res.json())
.then(jsonres => {
//console.log("Nominatim result: ", jsonres);
if (Array.isArray(jsonres) && jsonres.length > 0) {
updateProposal(jsonres[0]);
}
@ -110,10 +101,15 @@ export function EditLocationFilterExpression(props) {
function handleUseProposal() {
var new_filter = _.cloneDeep(filter);
new_filter.polygon = proposal;
new_filter.geo_area = proposal;
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
// own state inside (this is due to how Leaflet is integrated).
// We don't want to render a new MapView whenever our filter changes.
@ -124,11 +120,11 @@ export function EditLocationFilterExpression(props) {
<SearchBar onSubmit={onSearch} />
</Box>
<Box className={classes.mapcontainer}>
<MapView onMapChange={setMap}></MapView>
<MapView onMapChange={setMap} style={map_style}></MapView>
</Box>
<Box>
<Button variant="contained" onClick={handleUseProposal}>Use shown boundary</Button>
<Typography>Polygon with {filter.polygon.length} points.</Typography>
<Button variant="contained" onClick={handleUseProposal}>Use shown area</Button>
<Typography>Using: {filter.geo_area.display_name}</Typography>
</Box>
</>
);

Loading…
Cancel
Save