Better global polygon store.

master
Sander Vocke 6 years ago
parent 4c20f8f36e
commit 042401d61e
  1. 1
      package.json
  2. 27
      src/database.js
  3. 2
      src/gridgallery.js
  4. 21
      src/main.js
  5. 2
      src/map.js
  6. 35
      src/polygons.js
  7. 18
      src/queries.js
  8. 30
      src/userquerywidget.js
  9. 5
      yarn.lock

@ -21,6 +21,7 @@
"eslint-plugin-react-hooks": "^2.3.0",
"leaflet": "^1.6.0",
"lodash": "^4.17.15",
"object-hash": "^2.0.1",
"prop-types": "^15.6.0",
"react": "^16.12.0",
"react-dom": "^16.12.0",

@ -1,17 +1,19 @@
import React, { useEffect, useState } from 'react';
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';
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]);
//console.log("Query: ", queries[i]);
try {
sqljs_object.exec(queries[i]);
} catch (e) {
throw e;
}
}
console.log("Query: ", queries[queries.length - 1]);
//console.log("Query: ", queries[queries.length - 1]);
try {
var r = sqljs_object.exec(queries[queries.length - 1]);
} catch (e) {
@ -47,13 +49,13 @@ export function regexp_match(string, regex) {
return string.match(regex) != null;
}
export function is_in_geo_polygon(lat, long, polyid) {
return true;
export function is_in_polygon(x, y, poly) {
return false;
}
export function add_custom_functions(db) {
db.create_function("REGEXP", regexp_match);
db.create_function("IS_IN_GEO_POLYGON", is_in_geo_polygon);
export function is_in_geo_polygon_from_store(lat, long, polygon_hash) {
const poly = get_polygon_from_store(polygon_hash);
return is_in_polygon(lat, long, poly);
}
// Digikam stores its tree of tags as individual tags,
@ -102,8 +104,6 @@ export async function add_full_tag_info(db) {
db.exec(query);
});
console.log(db.exec("PRAGMA table_info([Tags]);"));
return db;
}
@ -116,7 +116,8 @@ export function ProvideDB(props) {
fetch_sqljs_db_from_sqlite(db_url)
.then(db => {
add_full_tag_info(db).then((newdb) => {
add_custom_functions(newdb);
db.create_function("REGEXP", regexp_match);
db.create_function("IS_IN_GEO_POLYGON", is_in_geo_polygon_from_store);
setError(false);
setDb(newdb);
})
@ -129,9 +130,5 @@ export function ProvideDB(props) {
db_error: error,
};
if (db != null) {
console.log("Provided DB tags schema:", db.exec("PRAGMA table_info([Tags]);"));
}
return children({ ...child_props });
}

@ -3,6 +3,8 @@ import Gallery from 'react-grid-gallery';
import { makeStyles } from '@material-ui/core/styles';
// TODO: some nice options: ModuloBox,
const useStyles = makeStyles(theme => ({
root: {
width: "100%",

@ -12,7 +12,6 @@ import { UserQuery, user_query_from_search_string, maybe_image_query, do_image_q
import { Browser } from './browser.js';
import { UserQueryWidget } from './userquerywidget.js';
import { ResultsView } from './resultsview.js';
import { PolygonStoreProvider } from './polygons.js';
const useStyles = makeStyles(theme => ({
root: {
@ -126,18 +125,16 @@ export function LoadedMainPage(props) {
return (
<>
<PolygonStoreProvider>
<Box className={classes.root}>
<Box className={classes.navigator}>
<Box className={classes.margined}>{albums && <Browser albums={allAlbums} tags={allTags} onNewQuery={onNewQuery} />}</Box>
</Box>
<Box className={classes.searchandview}>
<Box className={classes.margined}><SearchBar onSubmit={onSearch} /></Box>
<Box className={classes.margined}><UserQueryWidget userQuery={gallery_user_query} onChange={onNewQuery} /></Box>
<Box className={classes.margined}>{photos && <ResultsView photos={photos ? photos : []} albums={albums ? albums : []} tags={tags ? tags : []} />}</Box>
</Box>
<Box className={classes.root}>
<Box className={classes.navigator}>
<Box className={classes.margined}>{albums && <Browser albums={allAlbums} tags={allTags} onNewQuery={onNewQuery} />}</Box>
</Box>
</PolygonStoreProvider>
<Box className={classes.searchandview}>
<Box className={classes.margined}><SearchBar onSubmit={onSearch} /></Box>
<Box className={classes.margined}><UserQueryWidget userQuery={gallery_user_query} onChange={onNewQuery} /></Box>
<Box className={classes.margined}>{photos && <ResultsView photos={photos ? photos : []} albums={albums ? albums : []} tags={tags ? tags : []} />}</Box>
</Box>
</Box>
</>
);
}

@ -58,7 +58,7 @@ export function MapView(props) {
fetch("http://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(jsonres);
//console.log("Nominatim result: ", jsonres);
if (Array.isArray(jsonres) && jsonres.length > 0) {
showNominatimResult(jsonres[0]);
}

@ -1,21 +1,22 @@
import React, { createContext, useReducer } from 'react';
var g_PolygonStore = {};
const polygonStore = createContext({});
const { Provider } = polygonStore;
export function hash_polygon(polygon) {
var hash = require('object-hash');
return hash(polygon);
}
const PolygonStoreProvider = ({ children }) => {
const [state, dispatch] = useReducer((state, action) => {
switch (action.type) {
case 'add polygon':
const newstate = state;
newstate[action.payload.id] = action.payload.data;
return newstate;
default:
throw new Error();
};
}, {});
export function add_polygon_to_store(polygon) {
var h = hash_polygon(polygon);
if(!(h in g_PolygonStore)) {
g_PolygonStore[h] = polygon;
}
return <Provider value={{ state, dispatch }}>{children}</Provider>;
};
return h;
}
export { polygonStore, PolygonStoreProvider }
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,6 +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';
export function escape_regex(s) {
return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
@ -12,7 +13,7 @@ export function do_image_query(query, database, collection_path, collection_thum
var queries = [];
queries.push(query);
sqljs_async_queries(database, queries).then(res => {
console.log("response: ", res);
//console.log("Query result: ", res);
var photos = [];
if (res && Array.isArray(res) && res.length > 0) {
var cols = res[0].columns;
@ -61,7 +62,6 @@ export function do_tag_query(query, database) {
return new Promise(function (resolve, reject) {
var queries = [];
queries.push(query);
console.log("Provided DB tags schema before query:", database.exec("PRAGMA table_info([Tags]);"));
sqljs_async_queries(database, queries).then(res => {
var tags = [];
if (res && Array.isArray(res) && res.length > 0) {
@ -136,7 +136,6 @@ export class NegationFilter extends ResultFilter {
simplify() {
var f = this.body.simplify();
console.log("NOT body:", f);
if (f.is_true()) {
return new ConstFilter(this.result_type, false);
}
@ -248,7 +247,6 @@ export class MatchingFilter extends ResultFilter {
+ '(\/[^\/]+)*"))';
}
console.log(this);
throw new Error("Unsupported matching filter for SQL generation.");
}
simplify() { return this; }
@ -306,10 +304,9 @@ 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, polygon_store_dispatch) {
constructor(rtype, polygon) {
super(rtype);
this.polygon = polygon;
this.polygon_store_dispatch = polygon_store_dispatch;
}
to_sql_where() {
@ -319,13 +316,8 @@ 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 _ = require('lodash');
const poly = {
id: _.uniqueId('polygon_'),
data: this.polygon
}
this.polygon_store_dispatch({ type: 'add polygon', payload: poly });
return 'IS_IN_GEO_POLYGON(ImagePositions.latitude, ImagePositions.longitude, "' + poly.id + '")';
const hash = add_polygon_to_store(this.polygon);
return 'IS_IN_GEO_POLYGON(ImagePositions.latitude, ImagePositions.longitude, "' + hash + '")';
}
simplify() { return this; }

@ -24,8 +24,6 @@ import { format } from 'date-fns';
import { makeStyles } from '@material-ui/core/styles';
import { polygonStore } from './polygons.js';
import {
filter_is_const_false, ConstFilter, LogicalOperatorFilter, MatchingFilter,
ResultTypeEnum, LogicalOperatorEnum, MatchTypeEnum, NegationFilter, TimeFilterTypeEnum,
@ -239,8 +237,6 @@ export function EditFilterExpressionDialog(props) {
const { onClose, startingFilter, open } = props;
const [filter, setFilter] = React.useState(startingFilter);
const polygons_dispatch = useContext(polygonStore).dispatch;
const FilterTypeEnum = {
CONST: 0,
NEGATION: 1,
@ -270,7 +266,7 @@ export function EditFilterExpressionDialog(props) {
} else if (val == FilterTypeEnum.IMAGETYPE) {
setFilter(new ImageTypeFilter(filter.result_type, ImageTypeEnum.PHOTO));
} else if (val == FilterTypeEnum.LOCATION) {
setFilter(new LocationFilter(filter.result_type, [[0.0, 0.0]], polygons_dispatch));
setFilter(new LocationFilter(filter.result_type, [[0.0, 0.0]]));
} else {
throw new Error('Unsupported filter type: ' + val);
}
@ -483,12 +479,12 @@ export function LogicalOperatorFilterExpressionControl(props) {
<Box className={classes.logic_op_sbs}>
<Box>
<Box className={classes.logic_op_subexpr}>
<FilterExpressionControl expr={expr.sub_filter_a} onChange={handleAChanged} isRoot={false} />
<FilterExpressionControl expr={expr.sub_filter_a} onChange={handleAChanged} mayBeRemoved={true} />
</Box>
</Box>
<Box>
<Box className={classes.logic_op_subexpr}>
<FilterExpressionControl expr={expr.sub_filter_b} onChange={handleBChanged} isRoot={false} />
<FilterExpressionControl expr={expr.sub_filter_b} onChange={handleBChanged} mayBeRemoved={true} />
</Box>
</Box>
</Box>
@ -532,13 +528,16 @@ export function NegationExpressionControl(props) {
onChange(new_filter);
}
// Only nodes which are transformation nodes may normally be removed.
const may_be_removed = expr instanceof NegationFilter;
return (
<>
<Box className={classes.logic_op_outer}>
<Box className={classes.logic_op_sbs}>
<Box>
<Box className={classes.logic_op_subexpr}>
<FilterExpressionControl expr={expr.body} onChange={handleBodyChanged} isRoot={false} />
<FilterExpressionControl expr={expr.body} onChange={handleBodyChanged} mayBeRemoved={may_be_removed} />
</Box>
</Box>
</Box>
@ -624,7 +623,7 @@ export function LocationFilterExpressionControl(props) {
}
export function FilterExpressionControl(props) {
const { expr, onChange, isRoot } = props;
const { expr, onChange, mayBeRemoved } = props;
const [anchorEl, setAnchorEl] = React.useState(null);
const [editDialogOpen, setEditDialogOpen] = React.useState(false);
const [combineDialogOpen, setCombineDialogOpen] = React.useState(false);
@ -718,11 +717,6 @@ export function FilterExpressionControl(props) {
throw new Error('Unsupported filter expression');
}
// If this is the root node, removing it is not allowed.
// Other nodes may be removed.
// The only exception is a negation node: removing that will replace it by its child.
var allowRemove = !isRoot || (expr instanceof NegationFilter);
return (
<>
{filter_elem}
@ -737,7 +731,7 @@ export function FilterExpressionControl(props) {
<MenuItem onClick={handleAnd}>And...</MenuItem>
<MenuItem onClick={handleOr}>Or...</MenuItem>
<MenuItem onClick={handleNegation}>Negate</MenuItem>
{allowRemove && <MenuItem onClick={handleRemove}>Remove</MenuItem>}
{mayBeRemoved && <MenuItem onClick={handleRemove}>Remove</MenuItem>}
</Menu>
<EditFilterExpressionDialog
onClose={handleCloseEditFilterDialog}
@ -768,6 +762,10 @@ export function FilterControl(props) {
}
}
// The root node may in principle never be removed,
// except if it is a transformation node (then the body becomes the new root).
const may_be_removed = filter instanceof NegationFilter;
return (
<>
<Box className={classes.filtercontrol}>
@ -781,7 +779,7 @@ export function FilterControl(props) {
}
label={resultTypeString + ':'}
/>
<FilterExpressionControl expr={filter} onChange={onChange} isRoot={true} />
<FilterExpressionControl expr={filter} onChange={onChange} mayBeRemoved={may_be_removed} />
</Box>
</>
);

@ -7252,6 +7252,11 @@ object-hash@^1.3.1:
resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-1.3.1.tgz#fde452098a951cb145f039bb7d455449ddc126df"
integrity sha512-OSuu/pU4ENM9kmREg0BdNrUDIl1heYa4mBZacJc+vVWz4GtAwu7jO8s4AIt2aGRUTqxykpWzI3Oqnsm13tTMDA==
object-hash@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.0.1.tgz#cef18a0c940cc60aa27965ecf49b782cbf101d96"
integrity sha512-HgcGMooY4JC2PBt9sdUdJ6PMzpin+YtY3r/7wg0uTifP+HJWW8rammseSEHuyt0UeShI183UGssCJqm1bJR7QA==
object-inspect@^1.7.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.7.0.tgz#f4f6bd181ad77f006b5ece60bd0b6f398ff74a67"

Loading…
Cancel
Save