diff --git a/frontend/App.vue b/frontend/App.vue
index 7fee829..3166b5e 100644
--- a/frontend/App.vue
+++ b/frontend/App.vue
@@ -233,6 +233,7 @@
+
Searching for '{{ recent_search }}', found {{ hits.length }} places.
@@ -256,6 +257,39 @@
Click a result to continue.
+
+
+
+
+
+
+
+
item type filters
+
+
+
+
+
current item type filters
+
+
no item type filters
+
+
@@ -670,6 +704,10 @@ export default {
mode: undefined,
current_hit: undefined,
recent_search: undefined,
+ show_item_type_filter: false,
+ item_type_search: undefined,
+ item_type_hits: [],
+ item_type_filters: [],
};
},
computed: {
@@ -681,7 +719,7 @@ export default {
&& !this.current_item);
},
area_too_big() {
- return this.map_area > 1000 * 1000 * 1000;
+ return !this.item_type_filters.length && this.map_area > 1000 * 1000 * 1000;
},
too_many_items() {
return this.item_count > 1400;
@@ -760,6 +798,19 @@ export default {
edits(edit_list) {
this.update_unload_warning(edit_list);
},
+ item_type_search(value) {
+ if (value.length < 3) {
+ this.item_type_hits = [];
+ return;
+ }
+
+ var params = { q: value };
+ var isa_search_url = `${this.api_base_url}/api/1/isa_search`;
+
+ axios.get(isa_search_url, { params: params }).then((response) => {
+ this.item_type_hits = response.data.items;
+ });
+ },
selected_items(new_items, old_items) {
for (const qid of Object.keys(new_items)) {
if (!old_items[qid])
@@ -1283,11 +1334,17 @@ export default {
var params = { bounds: bounds.toBBoxString() };
+ if (this.item_type_filters.length) {
+ params["isa"] = this.item_type_filters.map(isa => isa.qid).join(",");
+ }
+
axios.get(items_url, { params: params }).then((response) => {
this.clear_isa();
this.isa_list = response.data.isa_count;
this.isa_list.forEach(isa => {
- if (this.detail_qid) this.isa_ticked.push(isa.qid);
+ if (this.detail_qid || this.item_type_filters.length) {
+ this.isa_ticked.push(isa.qid);
+ }
this.isa_labels[isa.qid] = isa.label;
this.isa_lookup[isa.qid] = isa;
});
@@ -1329,6 +1386,14 @@ export default {
return;
}
var params = { bounds: bounds.toBBoxString() };
+
+ console.log(this.item_type_filters.length);
+
+ if (this.item_type_filters.length) {
+ params["isa"] = this.item_type_filters.map(isa => isa.qid).join(",");
+ console.log(params.isa);
+ }
+
axios.get(count_url, { params: params }).then((response) => {
this.item_count = response.data.count;
if (!this.too_many_items) this.load_wikidata_items(bounds);
@@ -1390,7 +1455,9 @@ export default {
if (this.isa_lookup[isa.qid] === undefined) {
this.isa_lookup[isa.qid] = isa;
this.isa_list.push(isa);
- if (this.detail_qid) this.isa_ticked.push(isa.qid);
+ if (this.detail_qid || this.item_type_filters.length) {
+ this.isa_ticked.push(isa.qid);
+ }
} else {
this.isa_lookup[isa.qid].count += 1;
}
diff --git a/matcher/api.py b/matcher/api.py
index 1edc8bd..86dab52 100644
--- a/matcher/api.py
+++ b/matcher/api.py
@@ -155,8 +155,22 @@ def get_items_in_bbox(bbox):
return q
-def get_osm_with_wikidata_tag(bbox):
+def get_osm_with_wikidata_tag(bbox, isa_filter=None):
bbox_str = ','.join(str(v) for v in bbox)
+ extra_sql = ""
+ if isa_filter:
+ q = (
+ model.Item.query.join(model.ItemLocation)
+ .filter(func.ST_Covers(make_envelope(bbox),
+ model.ItemLocation.location))
+ )
+ q = add_isa_filter(q, isa_filter)
+ qids = [isa.qid for isa in q]
+ if not qids:
+ return []
+
+ qid_list = ",".join(f"'{qid}'" for qid in qids)
+ extra_sql += f" AND tags -> 'wikidata' in ({qid_list})"
# easier than building this query with SQLAlchemy
sql = f'''
@@ -178,7 +192,7 @@ UNION
HAVING st_area(st_collect(way)) < 20 * st_area(ST_MakeEnvelope({bbox_str}, {srid}))
) as anon
WHERE tags ? 'wikidata'
-'''
+''' + extra_sql
conn = database.session.connection()
result = conn.execute(text(sql))
@@ -263,17 +277,40 @@ def get_item_tags(item):
isa_items += [(isa, isa_path) for isa in get_items(isa_list)]
return {key: list(values) for key, values in osm_list.items()}
+def add_isa_filter(q, isa_qids):
+
+ q_subclass = database.session.query(model.Item.qid).filter(
+ func.jsonb_path_query_array(
+ model.Item.claims,
+ '$.P279[*].mainsnak.datavalue.value.id',
+ ).bool_op('?|')(list(isa_qids))
+ )
+
+ subclass_qid = {qid for qid, in q_subclass.all()}
+ # print(subclass_qid)
+
+ isa = func.jsonb_path_query_array(
+ model.Item.claims,
+ '$.P31[*].mainsnak.datavalue.value.id',
+ ).bool_op('?|')
+ return q.filter(isa(list(isa_qids | subclass_qid)))
+
+
+def wikidata_items_count(bounds, isa_filter=None):
-def wikidata_items_count(bounds):
q = (
model.Item.query.join(model.ItemLocation)
.filter(func.ST_Covers(make_envelope(bounds), model.ItemLocation.location))
)
+ if isa_filter:
+ q = add_isa_filter(q, isa_filter)
+
+ print(q.statement.compile(compile_kwargs={"literal_binds": True}))
+
return q.count()
def wikidata_isa_counts(bounds):
-
db_bbox = make_envelope(bounds)
q = (
@@ -632,9 +669,13 @@ def get_markers(all_items):
return [item_detail(item) for item in all_items if item]
-def wikidata_items(bounds):
+def wikidata_items(bounds, isa_filter=None):
check_is_street_number_first(get_bbox_centroid(bounds))
q = get_items_in_bbox(bounds)
+
+ if isa_filter:
+ q = add_isa_filter(q, isa_filter)
+
db_items = q.all()
items = get_markers(db_items)
@@ -692,3 +733,20 @@ def missing_wikidata_items(qids, lat, lon):
isa_count.append(isa)
return dict(items=items, isa_count=isa_count)
+
+def isa_incremental_search(search_terms):
+ en_label = func.jsonb_extract_path_text(model.Item.labels, "en", "value")
+ q = model.Item.query.filter(
+ model.Item.claims.has_key("P1282"),
+ en_label.ilike(f"%{search_terms}%"),
+ func.length(en_label) < 20,
+ )
+
+ ret = []
+ for item in q:
+ cur = {
+ "qid": item.qid,
+ "label": item.label(),
+ }
+ ret.append(cur)
+ return ret
diff --git a/web_view.py b/web_view.py
index 7ff7f61..2bd4add 100755
--- a/web_view.py
+++ b/web_view.py
@@ -271,6 +271,10 @@ def old_search_page():
def read_bounds_param():
return [float(i) for i in request.args["bounds"].split(",")]
+def read_isa_filter_param():
+ isa_param = request.args.get('isa')
+ if isa_param:
+ return set(qid.strip() for qid in isa_param.upper().split(','))
@app.route("/api/1/location")
def show_user_location():
@@ -280,18 +284,30 @@ def show_user_location():
@app.route("/api/1/count")
def api_wikidata_items_count():
t0 = time()
- count = api.wikidata_items_count(read_bounds_param())
+ isa_filter = read_isa_filter_param()
+ count = api.wikidata_items_count(read_bounds_param(), isa_filter=isa_filter)
t1 = time() - t0
return cors_jsonify(success=True, count=count, duration=t1)
+@app.route("/api/1/isa_search")
+def api_isa_search():
+ t0 = time()
+ search_terms = request.args.get("q")
+ items = api.isa_incremental_search(search_terms)
+ t1 = time() - t0
+
+ return cors_jsonify(success=True, items=items, duration=t1)
+
@app.route("/api/1/isa")
def api_wikidata_isa_counts():
t0 = time()
bounds = read_bounds_param()
- isa_count = api.wikidata_isa_counts(bounds)
+ isa_filter = read_isa_filter_param()
+
+ isa_count = api.wikidata_isa_counts(bounds, isa_filter=isa_filter)
t1 = time() - t0
return cors_jsonify(success=True, isa_count=isa_count, bounds=bounds, duration=t1)
@@ -302,7 +318,9 @@ def api_wikidata_items():
t0 = time()
bounds = read_bounds_param()
- ret = api.wikidata_items(bounds)
+ isa_filter = read_isa_filter_param()
+
+ ret = api.wikidata_items(bounds, isa_filter=isa_filter)
t1 = time() - t0
return cors_jsonify(success=True, duration=t1, **ret)
@@ -311,7 +329,8 @@ def api_wikidata_items():
@app.route("/api/1/osm")
def api_osm_objects():
t0 = time()
- objects = api.get_osm_with_wikidata_tag(read_bounds_param())
+ isa_filter = read_isa_filter_param()
+ objects = api.get_osm_with_wikidata_tag(read_bounds_param(), isa_filter=isa_filter)
t1 = time() - t0
return cors_jsonify(success=True, objects=objects, duration=t1)