forked from edward/owl-map
Improve OSM search
This commit is contained in:
parent
7c075227d7
commit
5a42e100c7
337
frontend/App.vue
337
frontend/App.vue
|
@ -36,7 +36,7 @@
|
||||||
</li>
|
</li>
|
||||||
</template>
|
</template>
|
||||||
<li v-else class="nav-item">
|
<li v-else class="nav-item">
|
||||||
<a class="nav-link" href="/login">Login</a>
|
<a class="nav-link" href="/login">Login with OpenStreetMap</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
@ -45,23 +45,21 @@
|
||||||
<div id="map">
|
<div id="map">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button ref="btn" id="load-btn" type="button" class="btn btn-primary btn-lg" @click="load_wikidata_items">
|
<button ref="btn" id="select-area-btn" type="button" class="btn btn-primary btn-lg" v-if="current_hit" @click.stop="select_area()">
|
||||||
<span v-if="!loading">
|
Select this area
|
||||||
Load Wikidata items
|
|
||||||
</span>
|
|
||||||
<span v-if="loading">
|
|
||||||
<span class="spinner-border spinner-border-sm"></span>
|
|
||||||
Loading ...
|
|
||||||
</span>
|
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
|
||||||
<div class="alert alert-primary alert-map" role="alert" v-if="area_too_big">
|
<div class="alert alert-primary alert-map" role="alert" v-if="area_too_big">
|
||||||
Zoom in to see Wikidata items on the map.
|
Zoom in to see Wikidata items on the map.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="alert alert-primary alert-map" role="alert" v-if="loading && !current_item">
|
||||||
|
Found {{ item_count }} Wikidata items. Updating markers. <span class="spinner-border spinner-border-sm"></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="alert alert-primary alert-map text-center" role="alert" v-if="!area_too_big && this.too_many_items">
|
<div class="alert alert-primary alert-map text-center" role="alert" v-if="!area_too_big && this.too_many_items">
|
||||||
Found {{ this.item_count }} Wikidata items, too many to show on the map.<br>
|
Found {{ this.item_count.toLocaleString('en-US') }} Wikidata items.<br/> Zoom in to see them.
|
||||||
Zoom in to see them.
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="edit-count" class="p-2" v-if="upload_state === undefined && edits.length">
|
<div id="edit-count" class="p-2" v-if="upload_state === undefined && edits.length">
|
||||||
|
@ -147,7 +145,7 @@
|
||||||
Changes saved.
|
Changes saved.
|
||||||
<a :href="`https://www.openstreetmap.org/changeset/${changeset_id}`"
|
<a :href="`https://www.openstreetmap.org/changeset/${changeset_id}`"
|
||||||
target="_blank">
|
target="_blank">
|
||||||
view your changeset
|
view your changeset <i class="fa fa-external-link"></i>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -218,7 +216,11 @@
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span v-if="osm.part_of">
|
<span v-if="osm.part_of">
|
||||||
<br>part of: {{ osm.part_of.join("; ") }}
|
<br>part of:
|
||||||
|
<span v-for="(part_of, part_of_index) in osm.part_of">
|
||||||
|
<span v-if="part_of_index != 0">, </span>
|
||||||
|
[{{ JSON.stringify(part_of.tags) }}]
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
@ -246,7 +248,7 @@
|
||||||
|
|
||||||
<div v-if="!current_item && !view_edits">
|
<div v-if="!current_item && !view_edits">
|
||||||
|
|
||||||
<div class="card m-2">
|
<div v-if="!edits.length" class="card m-2">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<form id="search-form" class="row row-cols-lg-auto g-3 align-items-center" @submit.prevent="run_search">
|
<form id="search-form" class="row row-cols-lg-auto g-3 align-items-center" @submit.prevent="run_search">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
|
@ -256,18 +258,75 @@
|
||||||
<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>
|
||||||
<div class="list-group" v-if="hits.length">
|
<div class="list-group" v-if="hits.length">
|
||||||
<a class="list-group-item list-group-item-action"
|
<a class="list-group-item list-group-item-action"
|
||||||
:class="{ active: hit.identifier == this.active_hit }"
|
:class="{ active: hit == this.current_hit }"
|
||||||
v-bind:key="hit.identifier"
|
v-bind:key="hit.identifier"
|
||||||
v-for="hit in hits"
|
v-for="hit in hits"
|
||||||
:href="hit_url(hit)"
|
:href="hit_url(hit)"
|
||||||
|
@mouseenter="show_hit_on_map(hit)"
|
||||||
@click.prevent="visit(hit)">
|
@click.prevent="visit(hit)">
|
||||||
{{ hit.name }} ({{ hit.category }})
|
{{ hit.name }} ({{ hit.label }})
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="alert alert-info mt-2" v-if="hits.length">
|
||||||
|
<i class="fa fa-info-circle"></i>
|
||||||
|
<span v-if="hits.length == 1">
|
||||||
|
One search result. Click the result to continue.
|
||||||
|
</span>
|
||||||
|
<span v-else>
|
||||||
|
Click a result to continue.
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card m-2" v-if="show_instructions">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="h3 card-title">Link Wikidata and OpenStreetMap</div>
|
||||||
|
<div class="alert alert-info">
|
||||||
|
<i class="fa fa-info-circle"></i>
|
||||||
|
This software is a beta, it works but is incomplete.<br/><a href="/documentation">See what's broken</a>.</div>
|
||||||
|
<p class="card-text">This tool will help you link Wikidata items with the matching object on OpenStreetMap (OSM).</p>
|
||||||
|
|
||||||
|
<p v-if="!username" class="card-text">To save changes you need to <a href="/login">login via OpenStreetMap</a>.</p>
|
||||||
|
|
||||||
|
<p class="card-text">Zoom in or search for an area to work on.</p>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
|
||||||
|
<p class="card-text">The map will show at most 400 items, if there are more then you need to zoom in before you can start editing.</p>
|
||||||
|
|
||||||
|
<p class="card-text">Wikidata items appear on the map as red or green markers. Items not linked for OSM appear in red, those that are linked appear in green. The map shows the location of Wikidata tagged OSM objects with a yellow pin.</p>
|
||||||
|
|
||||||
|
<p class="card-text">There are controls to filter what appears on the map. You have the option to hide Wikidata items that are already tagged on OSM. The type filter allows you to adjust what types of Wikidata item are displayed.</p>
|
||||||
|
|
||||||
|
<p class="card-text">Click on a marker to show details of the Wikidata item and find nearby possible OSM matches.</p>
|
||||||
|
-->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card m-2" v-if="!view_edits && isa_list.length">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="h5 card-title">Map key</div>
|
||||||
|
<ui class="list-group">
|
||||||
|
<li class="list-group-item">
|
||||||
|
<i style="background: #a23337; color: white" class="p-1 fa fa-wikidata"></i>
|
||||||
|
Wikidata item without OSM link
|
||||||
|
</li>
|
||||||
|
<li class="list-group-item">
|
||||||
|
<i style="background: #6dae40; color: white" class="p-1 fa fa-wikidata"></i>
|
||||||
|
Wikidata item with OSM link
|
||||||
|
</li>
|
||||||
|
<li class="list-group-item">
|
||||||
|
<i style="background: #f5bb39; color: white" class="p-1 fa fa-map"></i>
|
||||||
|
Linked OSM object
|
||||||
|
</li>
|
||||||
|
</ui>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div class="card m-2" v-if="!view_edits && isa_list.length">
|
<div class="card m-2" v-if="!view_edits && isa_list.length">
|
||||||
|
@ -342,6 +401,19 @@
|
||||||
<br>{{wd_item.closed.join('; ')}}
|
<br>{{wd_item.closed.join('; ')}}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input class="form-check-input" type="checkbox" id="debug" v-model="debug">
|
||||||
|
<label class="form-check-label" for="debug">debug</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="debug">
|
||||||
|
<a :href="`${api_base_url}/api/1/count?${bounds_param()}`" target="_blank">count</a> |
|
||||||
|
<a :href="`${api_base_url}/api/1/isa?${bounds_param()}`" target="_blank">IsA counts</a> |
|
||||||
|
<a :href="`${api_base_url}/api/1/item/${wd_item.qid}`" target="_blank">item</a> |
|
||||||
|
<a :href="`${api_base_url}/api/1/item/${wd_item.qid}/tags`" target="_blank">tags</a> |
|
||||||
|
<a :href="`${api_base_url}/api/1/item/${wd_item.qid}/candidates?${bounds_param()}`" target="_blank">candidates</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
<div class="col">
|
||||||
|
|
||||||
|
@ -354,7 +426,9 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<span v-if="wd_item.image_list.length">
|
<span v-if="wd_item.image_list.length">
|
||||||
|
<strong>Image from Wikidata</strong><br/>
|
||||||
<a href="#" data-bs-toggle="modal" data-bs-target="#imageModal">
|
<a href="#" data-bs-toggle="modal" data-bs-target="#imageModal">
|
||||||
<img class="w-100" :src="api_base_url + '/commons/' + wd_item.image_list[0]">
|
<img class="w-100" :src="api_base_url + '/commons/' + wd_item.image_list[0]">
|
||||||
</a>
|
</a>
|
||||||
|
@ -367,12 +441,30 @@
|
||||||
</div></div>
|
</div></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div v-if="!current_item.nearby" class="alert alert-info">
|
||||||
|
Searching for nearby OSM matches <span class="spinner-border spinner-border-sm"></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="current_item.nearby && !current_item.nearby.length">
|
||||||
|
<strong>No OSM matches found nearby</strong>
|
||||||
|
</div>
|
||||||
<div v-if="current_item.nearby && current_item.nearby.length">
|
<div v-if="current_item.nearby && current_item.nearby.length">
|
||||||
|
|
||||||
<strong>Possible OSM matches</strong><br>
|
<div v-if="!username" class="alert alert-info"><a href="/login">Login with OpenStreetMap</a> to add Wikidata tags</div>
|
||||||
<div class="form-check form-switch">
|
|
||||||
|
<strong>Possible OSM matches</strong> (sorted by distance from item)<br>
|
||||||
|
show:
|
||||||
|
<div class="form-check form-switch form-check-inline">
|
||||||
<input class="form-check-input" type="checkbox" id="show-tags" v-model="show_tags">
|
<input class="form-check-input" type="checkbox" id="show-tags" v-model="show_tags">
|
||||||
<label class="form-check-label" for="show-tags">show tags</label>
|
<label class="form-check-label" for="show-tags">tags</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check form-switch form-check-inline">
|
||||||
|
<input class="form-check-input" type="checkbox" id="show-area" v-model="show_area">
|
||||||
|
<label class="form-check-label" for="show-area">area</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check form-switch form-check-inline">
|
||||||
|
<input class="form-check-input" type="checkbox" id="show-presets" v-model="show_presets">
|
||||||
|
<label class="form-check-label" for="show-presets">type</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<table class="table table-sm table-hover" @mouseleave="this.current_osm = undefined">
|
<table class="table table-sm table-hover" @mouseleave="this.current_osm = undefined">
|
||||||
|
@ -380,7 +472,7 @@
|
||||||
<tr
|
<tr
|
||||||
v-for="osm in current_item.nearby"
|
v-for="osm in current_item.nearby"
|
||||||
class="osm-candidate"
|
class="osm-candidate"
|
||||||
:class="{ 'table-primary': osm.selected }"
|
:class="{ 'table-success': osm.selected }"
|
||||||
@mouseenter="this.current_osm=osm"
|
@mouseenter="this.current_osm=osm"
|
||||||
@click="select_osm(current_item, osm)">
|
@click="select_osm(current_item, osm)">
|
||||||
<td class="text-nowrap">
|
<td class="text-nowrap">
|
||||||
|
@ -392,7 +484,11 @@
|
||||||
@click.stop><i class="fa fa-map-o"></i></a>
|
@click.stop><i class="fa fa-map-o"></i></a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ osm.name || "no name" }}
|
<span class="badge bg-primary float-end">{{ osm.type }}</span>
|
||||||
|
<span v-if="osm.name">{{ osm.name }} </span>
|
||||||
|
<i v-else>no name </i>
|
||||||
|
<template v-if="show_presets && osm.presets.length">
|
||||||
|
<br>
|
||||||
<span v-for="(p, index) in osm.presets">
|
<span v-for="(p, index) in osm.presets">
|
||||||
<span v-if="index != 0">, </span>
|
<span v-if="index != 0">, </span>
|
||||||
<a
|
<a
|
||||||
|
@ -401,6 +497,7 @@
|
||||||
target="_blank"
|
target="_blank"
|
||||||
@click.stop>{{p.name}} <i class="fa fa-external-link"></i></a>
|
@click.stop>{{p.name}} <i class="fa fa-external-link"></i></a>
|
||||||
</span>
|
</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
<span v-if="osm.address && osm.address != osm.name">
|
<span v-if="osm.address && osm.address != osm.name">
|
||||||
<br>street address: {{ osm.address }}
|
<br>street address: {{ osm.address }}
|
||||||
|
@ -414,15 +511,19 @@
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span v-if="osm.part_of">
|
<span v-if="osm.part_of">
|
||||||
<br>part of: {{ osm.part_of.join("; ") }}
|
<br>part of:
|
||||||
|
<span v-for="(part_of, part_of_index) in osm.part_of">
|
||||||
|
<span v-if="part_of_index != 0">, </span>
|
||||||
|
{{ part_of.tags.name }}
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span v-if="osm.tags.ele">
|
<span v-if="osm.tags.ele">
|
||||||
<br>elevation: {{ osm.tags.ele }} m
|
<br>elevation: {{ osm.tags.ele }} m
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span v-if="osm.area && osm.area > 1000 * 1000">
|
<span v-if="show_area && osm.area && osm.area > 10 * 10">
|
||||||
<br>area: {{ (osm.area / (1000 * 1000)).toFixed(1) }} km²
|
<br>area: {{ format_area(osm.area) }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span v-if="osm.tags.wikidata">
|
<span v-if="osm.tags.wikidata">
|
||||||
|
@ -453,6 +554,7 @@
|
||||||
import L from "leaflet";
|
import L from "leaflet";
|
||||||
import { ExtraMarkers } from "leaflet-extra-markers";
|
import { ExtraMarkers } from "leaflet-extra-markers";
|
||||||
import axios from "redaxios";
|
import axios from "redaxios";
|
||||||
|
import {unref, toRaw} from 'vue';
|
||||||
|
|
||||||
var redMarker = ExtraMarkers.icon({
|
var redMarker = ExtraMarkers.icon({
|
||||||
icon: "fa-wikidata",
|
icon: "fa-wikidata",
|
||||||
|
@ -503,6 +605,8 @@ export default {
|
||||||
startZoom: Number,
|
startZoom: Number,
|
||||||
startRadius: Number,
|
startRadius: Number,
|
||||||
username: String,
|
username: String,
|
||||||
|
startMode: String,
|
||||||
|
q: String,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
@ -543,13 +647,25 @@ export default {
|
||||||
upload_state: undefined,
|
upload_state: undefined,
|
||||||
upload_progress: 0,
|
upload_progress: 0,
|
||||||
show_tags: false,
|
show_tags: false,
|
||||||
|
show_area: true,
|
||||||
|
show_presets: true,
|
||||||
flag_show_hover_isa: false,
|
flag_show_hover_isa: false,
|
||||||
debug: false,
|
debug: false,
|
||||||
map_area: undefined,
|
map_area: undefined,
|
||||||
item_count: undefined,
|
item_count: undefined,
|
||||||
|
mode: undefined,
|
||||||
|
current_hit: undefined,
|
||||||
|
recent_search: undefined,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
show_instructions() {
|
||||||
|
return (this.mode != "search"
|
||||||
|
&& !this.loading
|
||||||
|
&& !this.isa_list.length
|
||||||
|
&& !this.view_edits
|
||||||
|
&& !this.current_item);
|
||||||
|
},
|
||||||
area_too_big() {
|
area_too_big() {
|
||||||
return this.map_area > 1000 * 1000 * 1000;
|
return this.map_area > 1000 * 1000 * 1000;
|
||||||
},
|
},
|
||||||
|
@ -559,6 +675,9 @@ export default {
|
||||||
loading() {
|
loading() {
|
||||||
return this.osm_loading || this.wikidata_loading;
|
return this.osm_loading || this.wikidata_loading;
|
||||||
},
|
},
|
||||||
|
current_qid() {
|
||||||
|
return this.current_item ? this.current_item.wikidata.qid : undefined;
|
||||||
|
},
|
||||||
wd_item() {
|
wd_item() {
|
||||||
return this.current_item ? this.current_item.wikidata : undefined;
|
return this.current_item ? this.current_item.wikidata : undefined;
|
||||||
},
|
},
|
||||||
|
@ -610,7 +729,6 @@ export default {
|
||||||
|
|
||||||
if (!edit_lookup[qid]) {
|
if (!edit_lookup[qid]) {
|
||||||
qid_order.push(qid);
|
qid_order.push(qid);
|
||||||
console.log(edit.item);
|
|
||||||
edit_lookup[qid] = {
|
edit_lookup[qid] = {
|
||||||
'qid': qid,
|
'qid': qid,
|
||||||
'wikidata': edit.item.wikidata,
|
'wikidata': edit.item.wikidata,
|
||||||
|
@ -687,6 +805,20 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
format_area(area) {
|
||||||
|
var value, unit, dp;
|
||||||
|
if(area > 1000 * 1000) {
|
||||||
|
value = area / (1000 * 1000);
|
||||||
|
unit = "km²";
|
||||||
|
dp = 1;
|
||||||
|
} else {
|
||||||
|
value = area;
|
||||||
|
unit = "m²";
|
||||||
|
dp = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return value.toLocaleString("en-US", {maximumFractionDigits: dp}) + " " + unit
|
||||||
|
},
|
||||||
bounds_area(bounds) {
|
bounds_area(bounds) {
|
||||||
var width = bounds.getSouthWest().distanceTo(bounds.getSouthEast());
|
var width = bounds.getSouthWest().distanceTo(bounds.getSouthEast());
|
||||||
var height = bounds.getSouthWest().distanceTo(bounds.getNorthWest());
|
var height = bounds.getSouthWest().distanceTo(bounds.getNorthWest());
|
||||||
|
@ -752,8 +884,7 @@ export default {
|
||||||
break;
|
break;
|
||||||
case "progress":
|
case "progress":
|
||||||
var edit = app.edits[data.num];
|
var edit = app.edits[data.num];
|
||||||
app.upload_progress = ((edit.num + 1) * 100) / app.edits.length;
|
app.upload_progress = ((data.num + 1) * 100) / app.edits.length;
|
||||||
console.log(app.upload_progress);
|
|
||||||
edit.osm.upload_state = "progress";
|
edit.osm.upload_state = "progress";
|
||||||
break;
|
break;
|
||||||
case "saved":
|
case "saved":
|
||||||
|
@ -785,6 +916,7 @@ export default {
|
||||||
return index;
|
return index;
|
||||||
},
|
},
|
||||||
select_osm(item, osm) {
|
select_osm(item, osm) {
|
||||||
|
if (!this.username) return;
|
||||||
osm.selected = !osm.selected;
|
osm.selected = !osm.selected;
|
||||||
var index = this.edit_list_index(item, osm);
|
var index = this.edit_list_index(item, osm);
|
||||||
|
|
||||||
|
@ -825,7 +957,7 @@ export default {
|
||||||
var lng = c.lng.toFixed(5);
|
var lng = c.lng.toFixed(5);
|
||||||
var path = `/map/${zoom}/${lat}/${lng}`;
|
var path = `/map/${zoom}/${lat}/${lng}`;
|
||||||
if (this.current_item) {
|
if (this.current_item) {
|
||||||
path += `?item=${this.wd_item.qid}`;
|
path += `?item=${this.current_qid}`;
|
||||||
}
|
}
|
||||||
return path;
|
return path;
|
||||||
},
|
},
|
||||||
|
@ -897,18 +1029,43 @@ export default {
|
||||||
this.drop_hover_circles();
|
this.drop_hover_circles();
|
||||||
},
|
},
|
||||||
map_moved() {
|
map_moved() {
|
||||||
|
if (this.mode == "search") return;
|
||||||
this.auto_load();
|
this.auto_load();
|
||||||
this.update_map_path();
|
this.update_map_path();
|
||||||
},
|
},
|
||||||
|
current_state() {
|
||||||
|
var c = this.map.getCenter();
|
||||||
|
return {
|
||||||
|
mode: this.mode,
|
||||||
|
zoom: this.map.getZoom(),
|
||||||
|
lat: c.lat.toFixed(5),
|
||||||
|
lon: c.lng.toFixed(5),
|
||||||
|
search_text: this.search_text,
|
||||||
|
detail_qid: this.current_qid,
|
||||||
|
item_count: this.item_count,
|
||||||
|
map_area: this.map_area,
|
||||||
|
hits: toRaw(this.hits),
|
||||||
|
current_hit: toRaw(this.current_hit),
|
||||||
|
isa_ticked: toRaw(this.isa_ticked),
|
||||||
|
isa_labels: toRaw(this.isa_labels),
|
||||||
|
current_osm: toRaw(this.current_osm),
|
||||||
|
recent_search: this.recent_search,
|
||||||
|
};
|
||||||
|
},
|
||||||
update_map_path() {
|
update_map_path() {
|
||||||
history.replaceState(null, null, this.build_map_path());
|
var state = this.current_state();
|
||||||
|
history.replaceState(state, '', this.build_map_path());
|
||||||
},
|
},
|
||||||
open_item(qid) {
|
open_item(qid) {
|
||||||
var item = this.items[qid];
|
var item = this.items[qid];
|
||||||
|
if (this.current_item == item) return; // already open
|
||||||
this.view_edits = false;
|
this.view_edits = false;
|
||||||
this.current_osm = undefined;
|
this.current_osm = undefined;
|
||||||
this.current_item = item;
|
this.current_item = item;
|
||||||
this.update_map_path();
|
|
||||||
|
var state = this.current_state();
|
||||||
|
history.pushState(state, '', this.build_map_path());
|
||||||
|
|
||||||
this.hover_isa = undefined;
|
this.hover_isa = undefined;
|
||||||
|
|
||||||
if (item.detail_requested !== undefined) return;
|
if (item.detail_requested !== undefined) return;
|
||||||
|
@ -967,11 +1124,38 @@ export default {
|
||||||
var lon = parseFloat(hit.lon).toFixed(5);
|
var lon = parseFloat(hit.lon).toFixed(5);
|
||||||
return `/map/16/${lat}/${lon}`
|
return `/map/16/${lat}/${lon}`
|
||||||
},
|
},
|
||||||
visit(hit) {
|
fit_bounds_to_hit(hit) {
|
||||||
var lat = parseFloat(hit.lat).toFixed(5);
|
var bounds = [[hit.boundingbox[0], hit.boundingbox[2]],
|
||||||
var lon = parseFloat(hit.lon).toFixed(5);
|
[hit.boundingbox[1], hit.boundingbox[3]]];
|
||||||
|
this.map.fitBounds(bounds);
|
||||||
|
},
|
||||||
|
show_hit_on_map(hit) {
|
||||||
|
this.fit_bounds_to_hit(hit);
|
||||||
|
this.current_hit = hit;
|
||||||
|
this.update_search_state();
|
||||||
|
},
|
||||||
|
select_area() {
|
||||||
|
this.current_hit = undefined;
|
||||||
|
this.mode = "map";
|
||||||
|
this.hits = [];
|
||||||
|
this.search_text = "";
|
||||||
|
this.auto_load();
|
||||||
|
|
||||||
|
var state = this.current_state();
|
||||||
|
history.pushState(state, '', this.build_map_path());
|
||||||
|
},
|
||||||
|
|
||||||
|
visit(hit) {
|
||||||
|
this.current_hit = undefined;
|
||||||
|
this.hits = [];
|
||||||
|
this.recent_search = undefined;
|
||||||
|
this.search_text = "";
|
||||||
|
this.fit_bounds_to_hit(hit);
|
||||||
|
this.mode = "map";
|
||||||
|
|
||||||
|
var state = this.current_state();
|
||||||
|
history.pushState(state, '', this.build_map_path());
|
||||||
|
|
||||||
this.map.setView([lat, lon], 16);
|
|
||||||
this.auto_load();
|
this.auto_load();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -1074,7 +1258,6 @@ export default {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
auto_load(bounds) {
|
auto_load(bounds) {
|
||||||
console.log('auto_load');
|
|
||||||
var count_url = this.api_base_url + "/api/1/count";
|
var count_url = this.api_base_url + "/api/1/count";
|
||||||
bounds ||= this.map.getBounds();
|
bounds ||= this.map.getBounds();
|
||||||
this.map_area = this.bounds_area(bounds);
|
this.map_area = this.bounds_area(bounds);
|
||||||
|
@ -1089,12 +1272,30 @@ export default {
|
||||||
if (!this.too_many_items) this.load_wikidata_items(bounds);
|
if (!this.too_many_items) this.load_wikidata_items(bounds);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
update_search_state() {
|
||||||
|
history.replaceState(this.current_state(), '', "/search?q=" + this.search_text);
|
||||||
|
},
|
||||||
|
search_path() {
|
||||||
|
return "/search?q=" + this.search_text;
|
||||||
|
},
|
||||||
run_search() {
|
run_search() {
|
||||||
if (!this.search_text) return;
|
if (!this.search_text) return;
|
||||||
|
this.current_hit = undefined;
|
||||||
var params = { q: this.search_text };
|
var params = { q: this.search_text };
|
||||||
var search_url = this.api_base_url + "/api/1/search";
|
var api_search_url = this.api_base_url + "/api/1/search";
|
||||||
axios.get(search_url, { params: params }).then((response) => {
|
axios.get(api_search_url, { params: params }).then((response) => {
|
||||||
this.hits = response.data.hits;
|
this.hits = response.data.hits;
|
||||||
|
if (!this.hits.length) return;
|
||||||
|
|
||||||
|
this.recent_search = this.search_text;
|
||||||
|
this.item_count = undefined;
|
||||||
|
this.map_area = undefined;
|
||||||
|
this.clear_items();
|
||||||
|
this.mode = "search";
|
||||||
|
|
||||||
|
this.current_hit = this.hits[0];
|
||||||
|
this.fit_bounds_to_hit(this.current_hit);
|
||||||
|
history.pushState(this.current_state(), '', this.search_path());
|
||||||
});
|
});
|
||||||
|
|
||||||
},
|
},
|
||||||
|
@ -1107,7 +1308,6 @@ export default {
|
||||||
if (!item.wikidata) missing_qids.push(qid);
|
if (!item.wikidata) missing_qids.push(qid);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('missing:', missing_qids);
|
|
||||||
if (missing_qids.length == 0) {
|
if (missing_qids.length == 0) {
|
||||||
this.update_wikidata();
|
this.update_wikidata();
|
||||||
this.check_for_missing_done = true;
|
this.check_for_missing_done = true;
|
||||||
|
@ -1179,15 +1379,46 @@ export default {
|
||||||
marker_data.marker.setIcon(marker);
|
marker_data.marker.setIcon(marker);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
onpopstate(event) {
|
||||||
|
var state = event.state;
|
||||||
|
this.mode = state.mode;
|
||||||
|
this.zoom = state.zoom;
|
||||||
|
this.search_text = state.search_text;
|
||||||
|
this.center = [state.lat, state.lon];
|
||||||
|
this.detail_qid = state.detail_qid;
|
||||||
|
this.recent_search = state.recent_search;
|
||||||
|
if (!this.detail_qid) this.current_item = undefined;
|
||||||
|
|
||||||
|
this.item_count = state.item_count;
|
||||||
|
this.map_area = state.map_area;
|
||||||
|
this.hits = state.hits;
|
||||||
|
this.current_hit = state.current_hit;
|
||||||
|
|
||||||
|
/*
|
||||||
|
this.isa_ticked = state.isa_ticked;
|
||||||
|
this.isa_labels = state.isa_labels;
|
||||||
|
*/
|
||||||
|
|
||||||
|
this.current_osm = state.current_osm;
|
||||||
|
|
||||||
|
this.map.setView(this.center, this.zoom);
|
||||||
|
|
||||||
|
if (this.mode == "search") {
|
||||||
|
this.clear_items();
|
||||||
|
this.fit_bounds_to_hit(this.current_hit);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
},
|
||||||
created() {
|
created() {
|
||||||
var lat = this.startLat ?? 52.19679;
|
var lat = this.startLat ?? 52.19679;
|
||||||
var lon = this.startLon ?? 0.15224;
|
var lon = this.startLon ?? 0.15224;
|
||||||
this.center = [lat, lon];
|
this.center = [lat, lon];
|
||||||
this.zoom = this.startZoom;
|
this.zoom = this.startZoom;
|
||||||
|
this.mode = this.startMode;
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
|
||||||
this.$nextTick(function () {
|
this.$nextTick(function () {
|
||||||
var options = {
|
var options = {
|
||||||
center: this.center,
|
center: this.center,
|
||||||
|
@ -1205,7 +1436,6 @@ export default {
|
||||||
|
|
||||||
var bounds;
|
var bounds;
|
||||||
if (this.startRadius) {
|
if (this.startRadius) {
|
||||||
console.log('radius:', this.startRadius);
|
|
||||||
var bounds = L.latLng(this.center).toBounds(this.startRadius * 2000);
|
var bounds = L.latLng(this.center).toBounds(this.startRadius * 2000);
|
||||||
map.fitBounds(bounds);
|
map.fitBounds(bounds);
|
||||||
} else {
|
} else {
|
||||||
|
@ -1215,12 +1445,20 @@ export default {
|
||||||
map.on("moveend", this.map_moved);
|
map.on("moveend", this.map_moved);
|
||||||
this.map = map;
|
this.map = map;
|
||||||
|
|
||||||
|
if (this.mode == "search") {
|
||||||
|
this.search_text = this.q.trim();
|
||||||
|
this.run_search();
|
||||||
|
} else {
|
||||||
this.detail_qid = this.qid_from_url();
|
this.detail_qid = this.qid_from_url();
|
||||||
if (this.detail_qid) {
|
if (this.detail_qid) {
|
||||||
this.load_wikidata_items(bounds);
|
this.load_wikidata_items(bounds);
|
||||||
} else {
|
} else {
|
||||||
this.auto_load(bounds);
|
this.auto_load(bounds);
|
||||||
}
|
}
|
||||||
|
this.update_map_path();
|
||||||
|
}
|
||||||
|
|
||||||
|
window.onpopstate = this.onpopstate;
|
||||||
});
|
});
|
||||||
|
|
||||||
},
|
},
|
||||||
|
@ -1229,26 +1467,27 @@ export default {
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
||||||
|
#select-area-btn {
|
||||||
|
position: absolute;
|
||||||
|
top: 77px;
|
||||||
|
left: 70%;
|
||||||
|
transform: translate(-50%, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#map {
|
#map {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 57px;
|
top: 57px;
|
||||||
bottom: 0px;
|
bottom: 0px;
|
||||||
left: 35%;
|
left: 40%;
|
||||||
width: 65%;
|
width: 65%;
|
||||||
z-index: -1;
|
z-index: -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
#load-btn {
|
|
||||||
position: absolute;
|
|
||||||
top: 77px;
|
|
||||||
left: 67.5%;
|
|
||||||
transform: translate(-50%, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
.alert-map {
|
.alert-map {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 2rem;
|
bottom: 2rem;
|
||||||
left: 67.5%;
|
left: 70%;
|
||||||
transform: translate(-50%, 0);
|
transform: translate(-50%, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1280,7 +1519,7 @@ export default {
|
||||||
left: 0px;
|
left: 0px;
|
||||||
bottom: 0px;
|
bottom: 0px;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
width: 35%;
|
width: 40%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tag-card-body {
|
.tag-card-body {
|
||||||
|
|
|
@ -228,7 +228,10 @@ class MapMixin:
|
||||||
|
|
||||||
@declared_attr
|
@declared_attr
|
||||||
def geojson_str(cls):
|
def geojson_str(cls):
|
||||||
return column_property(func.ST_AsGeoJSON(cls.way), deferred=True)
|
return column_property(
|
||||||
|
func.ST_AsGeoJSON(cls.way, maxdecimaldigits=6),
|
||||||
|
deferred=True
|
||||||
|
)
|
||||||
|
|
||||||
@declared_attr
|
@declared_attr
|
||||||
def as_EWKT(cls):
|
def as_EWKT(cls):
|
||||||
|
@ -289,6 +292,11 @@ class Line(MapMixin, Base):
|
||||||
class Polygon(MapMixin, Base):
|
class Polygon(MapMixin, Base):
|
||||||
way_area = Column(Float)
|
way_area = Column(Float)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_osm(cls, osm_type, osm_id):
|
||||||
|
src_id = osm_id * {'way': 1, 'relation': -1}[osm_type]
|
||||||
|
return cls.query.get(src_id)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def type(self):
|
def type(self):
|
||||||
return "way" if self.src_id > 0 else "relation"
|
return "way" if self.src_id > 0 else "relation"
|
||||||
|
|
|
@ -23,6 +23,8 @@
|
||||||
startZoom: {{ zoom }},
|
startZoom: {{ zoom }},
|
||||||
startRadius: {{ radius | tojson }},
|
startRadius: {{ radius | tojson }},
|
||||||
username: {{ username | tojson }},
|
username: {{ username | tojson }},
|
||||||
|
startMode: {{ mode | tojson }},
|
||||||
|
q: {{ q | tojson }},
|
||||||
};
|
};
|
||||||
|
|
||||||
const app = createApp(App, props).mount('#app');
|
const app = createApp(App, props).mount('#app');
|
||||||
|
|
28
web_view.py
28
web_view.py
|
@ -177,6 +177,26 @@ def map_start_page():
|
||||||
ip=request.args.get('ip'),
|
ip=request.args.get('ip'),
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/search")
|
||||||
|
def search_page():
|
||||||
|
loc = get_user_location()
|
||||||
|
q = request.args.get('q')
|
||||||
|
|
||||||
|
user = flask_login.current_user
|
||||||
|
username = user.username if user.is_authenticated else None
|
||||||
|
|
||||||
|
return render_template(
|
||||||
|
"map.html",
|
||||||
|
lat=f'{loc["latitude"]:.5f}',
|
||||||
|
lon=f'{loc["longitude"]:.5f}',
|
||||||
|
zoom=16,
|
||||||
|
radius=loc["accuracy_radius"],
|
||||||
|
username=username,
|
||||||
|
mode="search",
|
||||||
|
q=q,
|
||||||
|
)
|
||||||
|
|
||||||
@app.route("/map/<int:zoom>/<float(signed=True):lat>/<float(signed=True):lon>")
|
@app.route("/map/<int:zoom>/<float(signed=True):lat>/<float(signed=True):lon>")
|
||||||
def map_location(zoom, lat, lon):
|
def map_location(zoom, lat, lon):
|
||||||
user = flask_login.current_user
|
user = flask_login.current_user
|
||||||
|
@ -188,7 +208,9 @@ def map_location(zoom, lat, lon):
|
||||||
lat=lat,
|
lat=lat,
|
||||||
lon=lon,
|
lon=lon,
|
||||||
radius=request.args.get('radius'),
|
radius=request.args.get('radius'),
|
||||||
username=username
|
username=username,
|
||||||
|
mode="map",
|
||||||
|
q=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -215,8 +237,8 @@ def search_map_page():
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/search")
|
@app.route("/old_search")
|
||||||
def search_page():
|
def old_search_page():
|
||||||
q = request.args.get("q")
|
q = request.args.get("q")
|
||||||
if not q:
|
if not q:
|
||||||
return render_template("search.html", hits=None, bbox_list=None)
|
return render_template("search.html", hits=None, bbox_list=None)
|
||||||
|
|
Loading…
Reference in a new issue