forked from edward/owl-map
Add item type filter
This commit is contained in:
parent
a791de7a24
commit
9b7f6ee878
|
@ -233,6 +233,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<button type="submit" id="search-btn" class="btn btn-primary">search</button>
|
<button type="submit" id="search-btn" class="btn btn-primary">search</button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<p v-if="recent_search" class="card-text mt-2">Searching for '{{ recent_search }}', found {{ hits.length }} places.</p>
|
<p v-if="recent_search" class="card-text mt-2">Searching for '{{ recent_search }}', found {{ hits.length }} places.</p>
|
||||||
|
@ -256,6 +257,39 @@
|
||||||
Click a result to continue.
|
Click a result to continue.
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input class="form-check-input" type="checkbox" id="type-filter" v-model="show_item_type_filter">
|
||||||
|
<label class="form-check-label" for="type-filter">item type filter</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card" v-if="show_item_type_filter">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">item type filters</h5>
|
||||||
|
<input class="form-control" v-model.trim="item_type_search" placeholder="item type">
|
||||||
|
|
||||||
|
<div class="list-group" v-if="item_type_hits.length">
|
||||||
|
<a class="list-group-item"
|
||||||
|
v-bind:key="isa.qid"
|
||||||
|
v-for="isa in item_type_hits"
|
||||||
|
href="#"
|
||||||
|
@click.prevent="item_type_filters.includes(isa) || item_type_filters.push(isa)"
|
||||||
|
>
|
||||||
|
{{ isa.label }} ({{ isa.qid }})
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h6>current item type filters</h6>
|
||||||
|
<div class="list-group" v-if="item_type_filters.length">
|
||||||
|
<a class="list-group-item" v-bind:key="isa.qid" v-for="(isa, index) in item_type_filters" href="#">
|
||||||
|
{{ isa.label }} ({{ isa.qid }})
|
||||||
|
<button type="button"
|
||||||
|
class="btn btn-danger btn-sm"
|
||||||
|
@click="item_type_filters.splice(index, 1)">remove</button>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div v-else>no item type filters</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -670,6 +704,10 @@ export default {
|
||||||
mode: undefined,
|
mode: undefined,
|
||||||
current_hit: undefined,
|
current_hit: undefined,
|
||||||
recent_search: undefined,
|
recent_search: undefined,
|
||||||
|
show_item_type_filter: false,
|
||||||
|
item_type_search: undefined,
|
||||||
|
item_type_hits: [],
|
||||||
|
item_type_filters: [],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -681,7 +719,7 @@ export default {
|
||||||
&& !this.current_item);
|
&& !this.current_item);
|
||||||
},
|
},
|
||||||
area_too_big() {
|
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() {
|
too_many_items() {
|
||||||
return this.item_count > 1400;
|
return this.item_count > 1400;
|
||||||
|
@ -760,6 +798,19 @@ export default {
|
||||||
edits(edit_list) {
|
edits(edit_list) {
|
||||||
this.update_unload_warning(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) {
|
selected_items(new_items, old_items) {
|
||||||
for (const qid of Object.keys(new_items)) {
|
for (const qid of Object.keys(new_items)) {
|
||||||
if (!old_items[qid])
|
if (!old_items[qid])
|
||||||
|
@ -1283,11 +1334,17 @@ export default {
|
||||||
|
|
||||||
var params = { bounds: bounds.toBBoxString() };
|
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) => {
|
axios.get(items_url, { params: params }).then((response) => {
|
||||||
this.clear_isa();
|
this.clear_isa();
|
||||||
this.isa_list = response.data.isa_count;
|
this.isa_list = response.data.isa_count;
|
||||||
this.isa_list.forEach(isa => {
|
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_labels[isa.qid] = isa.label;
|
||||||
this.isa_lookup[isa.qid] = isa;
|
this.isa_lookup[isa.qid] = isa;
|
||||||
});
|
});
|
||||||
|
@ -1329,6 +1386,14 @@ export default {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var params = { bounds: bounds.toBBoxString() };
|
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) => {
|
axios.get(count_url, { params: params }).then((response) => {
|
||||||
this.item_count = response.data.count;
|
this.item_count = response.data.count;
|
||||||
if (!this.too_many_items) this.load_wikidata_items(bounds);
|
if (!this.too_many_items) this.load_wikidata_items(bounds);
|
||||||
|
@ -1390,7 +1455,9 @@ export default {
|
||||||
if (this.isa_lookup[isa.qid] === undefined) {
|
if (this.isa_lookup[isa.qid] === undefined) {
|
||||||
this.isa_lookup[isa.qid] = isa;
|
this.isa_lookup[isa.qid] = isa;
|
||||||
this.isa_list.push(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 {
|
} else {
|
||||||
this.isa_lookup[isa.qid].count += 1;
|
this.isa_lookup[isa.qid].count += 1;
|
||||||
}
|
}
|
||||||
|
|
|
@ -155,8 +155,22 @@ def get_items_in_bbox(bbox):
|
||||||
return q
|
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)
|
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
|
# easier than building this query with SQLAlchemy
|
||||||
sql = f'''
|
sql = f'''
|
||||||
|
@ -178,7 +192,7 @@ UNION
|
||||||
HAVING st_area(st_collect(way)) < 20 * st_area(ST_MakeEnvelope({bbox_str}, {srid}))
|
HAVING st_area(st_collect(way)) < 20 * st_area(ST_MakeEnvelope({bbox_str}, {srid}))
|
||||||
) as anon
|
) as anon
|
||||||
WHERE tags ? 'wikidata'
|
WHERE tags ? 'wikidata'
|
||||||
'''
|
''' + extra_sql
|
||||||
conn = database.session.connection()
|
conn = database.session.connection()
|
||||||
result = conn.execute(text(sql))
|
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)]
|
isa_items += [(isa, isa_path) for isa in get_items(isa_list)]
|
||||||
return {key: list(values) for key, values in osm_list.items()}
|
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 = (
|
q = (
|
||||||
model.Item.query.join(model.ItemLocation)
|
model.Item.query.join(model.ItemLocation)
|
||||||
.filter(func.ST_Covers(make_envelope(bounds), model.ItemLocation.location))
|
.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()
|
return q.count()
|
||||||
|
|
||||||
def wikidata_isa_counts(bounds):
|
def wikidata_isa_counts(bounds):
|
||||||
|
|
||||||
db_bbox = make_envelope(bounds)
|
db_bbox = make_envelope(bounds)
|
||||||
|
|
||||||
q = (
|
q = (
|
||||||
|
@ -632,9 +669,13 @@ def get_markers(all_items):
|
||||||
return [item_detail(item) for item in all_items if item]
|
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))
|
check_is_street_number_first(get_bbox_centroid(bounds))
|
||||||
q = get_items_in_bbox(bounds)
|
q = get_items_in_bbox(bounds)
|
||||||
|
|
||||||
|
if isa_filter:
|
||||||
|
q = add_isa_filter(q, isa_filter)
|
||||||
|
|
||||||
db_items = q.all()
|
db_items = q.all()
|
||||||
items = get_markers(db_items)
|
items = get_markers(db_items)
|
||||||
|
|
||||||
|
@ -692,3 +733,20 @@ def missing_wikidata_items(qids, lat, lon):
|
||||||
isa_count.append(isa)
|
isa_count.append(isa)
|
||||||
|
|
||||||
return dict(items=items, isa_count=isa_count)
|
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
|
||||||
|
|
27
web_view.py
27
web_view.py
|
@ -271,6 +271,10 @@ def old_search_page():
|
||||||
def read_bounds_param():
|
def read_bounds_param():
|
||||||
return [float(i) for i in request.args["bounds"].split(",")]
|
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")
|
@app.route("/api/1/location")
|
||||||
def show_user_location():
|
def show_user_location():
|
||||||
|
@ -280,18 +284,30 @@ def show_user_location():
|
||||||
@app.route("/api/1/count")
|
@app.route("/api/1/count")
|
||||||
def api_wikidata_items_count():
|
def api_wikidata_items_count():
|
||||||
t0 = time()
|
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
|
t1 = time() - t0
|
||||||
return cors_jsonify(success=True, count=count, duration=t1)
|
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")
|
@app.route("/api/1/isa")
|
||||||
def api_wikidata_isa_counts():
|
def api_wikidata_isa_counts():
|
||||||
t0 = time()
|
t0 = time()
|
||||||
|
|
||||||
bounds = read_bounds_param()
|
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
|
t1 = time() - t0
|
||||||
return cors_jsonify(success=True, isa_count=isa_count, bounds=bounds, duration=t1)
|
return cors_jsonify(success=True, isa_count=isa_count, bounds=bounds, duration=t1)
|
||||||
|
@ -302,7 +318,9 @@ def api_wikidata_items():
|
||||||
t0 = time()
|
t0 = time()
|
||||||
|
|
||||||
bounds = read_bounds_param()
|
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
|
t1 = time() - t0
|
||||||
return cors_jsonify(success=True, duration=t1, **ret)
|
return cors_jsonify(success=True, duration=t1, **ret)
|
||||||
|
@ -311,7 +329,8 @@ def api_wikidata_items():
|
||||||
@app.route("/api/1/osm")
|
@app.route("/api/1/osm")
|
||||||
def api_osm_objects():
|
def api_osm_objects():
|
||||||
t0 = time()
|
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
|
t1 = time() - t0
|
||||||
return cors_jsonify(success=True, objects=objects, duration=t1)
|
return cors_jsonify(success=True, objects=objects, duration=t1)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue