Switch to maxminddb for user location.
This commit is contained in:
parent
792bfa176d
commit
f60aa70472
132
frontend/App.vue
132
frontend/App.vue
|
@ -48,6 +48,15 @@
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<div class="alert alert-primary alert-map" role="alert" v-if="area_too_big">
|
||||||
|
Zoom in to see Wikidata items on the map.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
Zoom in to see them.
|
||||||
|
</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">
|
||||||
<span>edits: {{ edits.length }}</span>
|
<span>edits: {{ edits.length }}</span>
|
||||||
<button class="btn btn-primary btn-sm ms-2" @click="close_item(); view_edits=true">
|
<button class="btn btn-primary btn-sm ms-2" @click="close_item(); view_edits=true">
|
||||||
|
@ -172,8 +181,8 @@
|
||||||
<table class="table table-sm table-hover">
|
<table class="table table-sm table-hover">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr v-for="osm in edit.osm" class="osm-candidate">
|
<tr v-for="osm in edit.osm" class="osm-candidate">
|
||||||
<td class="text-end text-nowrap">
|
<td class="text-end">
|
||||||
{{ osm.distance.toFixed(0) }}m
|
<span class="text-nowrap">{{ osm.distance.toFixed(0) }}m</span><br>
|
||||||
<a
|
<a
|
||||||
:href="'https://www.openstreetmap.org/' + osm.identifier"
|
:href="'https://www.openstreetmap.org/' + osm.identifier"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
|
@ -352,7 +361,13 @@
|
||||||
</div>
|
</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>
|
<strong>Possible OSM matches</strong><br>
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<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>
|
||||||
|
</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">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr
|
<tr
|
||||||
|
@ -361,10 +376,8 @@
|
||||||
:class="{ 'table-primary': osm.selected }"
|
:class="{ 'table-primary': 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>
|
<td class="text-nowrap">
|
||||||
<input class="form-check-input" type="checkbox" v-model="osm.selected"/>
|
<input class="form-check-input" type="checkbox" v-model="osm.selected" v-if="username" />
|
||||||
</td>
|
|
||||||
<td class="text-end text-nowrap">
|
|
||||||
{{ osm.distance.toFixed(0) }}m
|
{{ osm.distance.toFixed(0) }}m
|
||||||
<a
|
<a
|
||||||
:href="'https://www.openstreetmap.org/' + osm.identifier"
|
:href="'https://www.openstreetmap.org/' + osm.identifier"
|
||||||
|
@ -397,6 +410,27 @@
|
||||||
<br>part of: {{ osm.part_of.join("; ") }}
|
<br>part of: {{ osm.part_of.join("; ") }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
|
<span v-if="osm.tags.ele">
|
||||||
|
<br>elevation: {{ osm.tags.ele }} m
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span v-if="osm.area && osm.area > 1000 * 1000">
|
||||||
|
<br>area: {{ (osm.area / (1000 * 1000)).toFixed(1) }} km²
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span v-if="osm.tags.wikidata">
|
||||||
|
<br>Wikidata tag:
|
||||||
|
<a :href="`https://wikidata.org/wiki/${osm.tags.wikidata}`">{{ osm.tags.wikidata }}</a>
|
||||||
|
</span>
|
||||||
|
<div class="card" v-if="show_tags">
|
||||||
|
<div class="card-body tag-card-body">
|
||||||
|
<span class="badge bg-secondary float-end">tags</span>
|
||||||
|
<div class="card-text" v-for="(value, key) of osm.tags">
|
||||||
|
<strong>{{ key }}</strong>:
|
||||||
|
{{ value.replace(/;/g, '; ') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
@ -460,6 +494,7 @@ export default {
|
||||||
startLat: Number,
|
startLat: Number,
|
||||||
startLon: Number,
|
startLon: Number,
|
||||||
startZoom: Number,
|
startZoom: Number,
|
||||||
|
startRadius: Number,
|
||||||
username: String,
|
username: String,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
|
@ -500,9 +535,20 @@ export default {
|
||||||
changeset_id: undefined,
|
changeset_id: undefined,
|
||||||
upload_state: undefined,
|
upload_state: undefined,
|
||||||
upload_progress: 0,
|
upload_progress: 0,
|
||||||
|
show_tags: false,
|
||||||
|
flag_show_hover_isa: false,
|
||||||
|
debug: false,
|
||||||
|
map_area: undefined,
|
||||||
|
item_count: undefined,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
area_too_big() {
|
||||||
|
return this.map_area > 1000 * 1000 * 1000;
|
||||||
|
},
|
||||||
|
too_many_items() {
|
||||||
|
return this.item_count > 400;
|
||||||
|
},
|
||||||
loading() {
|
loading() {
|
||||||
return this.osm_loading || this.wikidata_loading;
|
return this.osm_loading || this.wikidata_loading;
|
||||||
},
|
},
|
||||||
|
@ -610,29 +656,39 @@ export default {
|
||||||
|
|
||||||
item.markers.forEach((marker) => {
|
item.markers.forEach((marker) => {
|
||||||
var coords = marker.getLatLng();
|
var coords = marker.getLatLng();
|
||||||
var circle = L.circle(coords, { radius: 20, color: "orange" }).addTo(this.map);
|
var circle = L.circleMarker(coords, { radius: 20, color: "orange" }).addTo(this.map);
|
||||||
this.selected_circles.push(circle);
|
this.selected_circles.push(circle);
|
||||||
});
|
});
|
||||||
|
|
||||||
},
|
},
|
||||||
hover_isa(highlight_isa) {
|
hover_isa(highlight_isa) {
|
||||||
|
// if (!this.flag_show_hover_isa) return;
|
||||||
this.drop_hover_circles();
|
this.drop_hover_circles();
|
||||||
|
|
||||||
for(const item of Object.values(this.selected_items)) {
|
for(const item of Object.values(this.selected_items)) {
|
||||||
var opacity = 0.9;
|
// var opacity = 0.9;
|
||||||
if (highlight_isa) {
|
if (highlight_isa) {
|
||||||
var match = item.wikidata.isa_list.some(isa => isa == highlight_isa.qid);
|
var match = item.wikidata.isa_list.some(isa => isa == highlight_isa.qid);
|
||||||
opacity = match ? 1 : 0.2;
|
// opacity = match ? 1 : 0.2;
|
||||||
if (match) {
|
if (match) {
|
||||||
this.add_hover_circles(item);
|
this.add_hover_circles(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.set_item_opacity(item, opacity);
|
// this.set_item_opacity(item, opacity);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
bounds_area(bounds) {
|
||||||
|
var width = bounds.getSouthWest().distanceTo(bounds.getSouthEast());
|
||||||
|
var height = bounds.getSouthWest().distanceTo(bounds.getNorthWest());
|
||||||
|
|
||||||
|
return width * height;
|
||||||
|
},
|
||||||
|
bounds_param() {
|
||||||
|
return 'bounds=' + this.map.getBounds().toBBoxString();
|
||||||
|
},
|
||||||
close_edit_list() {
|
close_edit_list() {
|
||||||
this.view_edits = false;
|
this.view_edits = false;
|
||||||
if (this.upload_state == 'done') {
|
if (this.upload_state == 'done') {
|
||||||
|
@ -797,7 +853,7 @@ export default {
|
||||||
add_hover_circles(item) {
|
add_hover_circles(item) {
|
||||||
item.markers.forEach((marker) => {
|
item.markers.forEach((marker) => {
|
||||||
var coords = marker.getLatLng();
|
var coords = marker.getLatLng();
|
||||||
var circle = L.circle(coords, { radius: 20 }).addTo(this.map);
|
var circle = L.circleMarker(coords, { radius: 20 }).addTo(this.map);
|
||||||
this.hover_circles.push(circle);
|
this.hover_circles.push(circle);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -946,10 +1002,10 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.items = {};
|
this.items = {};
|
||||||
clear_isa();
|
this.clear_isa();
|
||||||
},
|
},
|
||||||
|
|
||||||
load_wikidata_items() {
|
load_wikidata_items(bounds) {
|
||||||
this.load_button_pressed = true;
|
this.load_button_pressed = true;
|
||||||
this.wikidata_loaded = false;
|
this.wikidata_loaded = false;
|
||||||
this.osm_loaded = false;
|
this.osm_loaded = false;
|
||||||
|
@ -958,8 +1014,7 @@ export default {
|
||||||
this.wikidata_loading = true;
|
this.wikidata_loading = true;
|
||||||
this.osm_loading = true;
|
this.osm_loading = true;
|
||||||
|
|
||||||
|
bounds ||= this.map.getBounds();
|
||||||
var bounds = this.map.getBounds();
|
|
||||||
|
|
||||||
var items_url = this.api_base_url + "/api/1/items";
|
var items_url = this.api_base_url + "/api/1/items";
|
||||||
var osm_objects_url = this.api_base_url + "/api/1/osm";
|
var osm_objects_url = this.api_base_url + "/api/1/osm";
|
||||||
|
@ -1011,16 +1066,20 @@ export default {
|
||||||
this.hits = [];
|
this.hits = [];
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
auto_load() {
|
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";
|
||||||
var bounds = this.map.getBounds();
|
bounds ||= this.map.getBounds();
|
||||||
|
this.map_area = this.bounds_area(bounds);
|
||||||
|
if (this.area_too_big) {
|
||||||
|
this.item_count = undefined;
|
||||||
|
if (this.items) this.clear_items();
|
||||||
|
return;
|
||||||
|
}
|
||||||
var params = { bounds: bounds.toBBoxString() };
|
var params = { bounds: bounds.toBBoxString() };
|
||||||
axios.get(count_url, { params: params }).then((response) => {
|
axios.get(count_url, { params: params }).then((response) => {
|
||||||
var count = response.data.count;
|
this.item_count = response.data.count;
|
||||||
if (count < 1000) {
|
if (!this.too_many_items) this.load_wikidata_items(bounds);
|
||||||
this.load_wikidata_items();
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
run_search() {
|
run_search() {
|
||||||
|
@ -1119,15 +1178,16 @@ export default {
|
||||||
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 || 16;
|
this.zoom = this.startZoom;
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.$nextTick(function () {
|
this.$nextTick(function () {
|
||||||
var options = {
|
var options = {
|
||||||
center: this.center,
|
center: this.center,
|
||||||
zoom: this.zoom,
|
zoom: this.zoom || 16,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
var map = L.map("map", options);
|
var map = L.map("map", options);
|
||||||
var osm_url = "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png";
|
var osm_url = "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png";
|
||||||
var tile_url = "https://tile-c.openstreetmap.fr/hot/{z}/{x}/{y}.png";
|
var tile_url = "https://tile-c.openstreetmap.fr/hot/{z}/{x}/{y}.png";
|
||||||
|
@ -1136,14 +1196,23 @@ export default {
|
||||||
});
|
});
|
||||||
osm.addTo(map);
|
osm.addTo(map);
|
||||||
|
|
||||||
|
var bounds;
|
||||||
|
if (this.startRadius) {
|
||||||
|
console.log('radius:', this.startRadius);
|
||||||
|
var bounds = L.latLng(this.center).toBounds(this.startRadius * 2000);
|
||||||
|
map.fitBounds(bounds);
|
||||||
|
} else {
|
||||||
|
bounds = map.getBounds();
|
||||||
|
}
|
||||||
|
|
||||||
map.on("moveend", this.map_moved);
|
map.on("moveend", this.map_moved);
|
||||||
this.map = map;
|
this.map = map;
|
||||||
|
|
||||||
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();
|
this.load_wikidata_items(bounds);
|
||||||
} else {
|
} else {
|
||||||
this.auto_load();
|
this.auto_load(bounds);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1174,6 +1243,13 @@ export default {
|
||||||
transform: translate(-50%, 0);
|
transform: translate(-50%, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.alert-map {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 2rem;
|
||||||
|
left: 67.5%;
|
||||||
|
transform: translate(-50%, 0);
|
||||||
|
}
|
||||||
|
|
||||||
#edit-count {
|
#edit-count {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 77px;
|
top: 77px;
|
||||||
|
@ -1205,4 +1281,8 @@ export default {
|
||||||
width: 35%;
|
width: 35%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tag-card-body {
|
||||||
|
padding: 0.5rem 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
startLat: {{ lat }},
|
startLat: {{ lat }},
|
||||||
startLon: {{ lon }},
|
startLon: {{ lon }},
|
||||||
startZoom: {{ zoom }},
|
startZoom: {{ zoom }},
|
||||||
|
startRadius: {{ radius | tojson }},
|
||||||
username: {{ username | tojson }},
|
username: {{ username | tojson }},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
25
web_view.py
25
web_view.py
|
@ -12,6 +12,7 @@ import flask_login
|
||||||
import json
|
import json
|
||||||
import GeoIP
|
import GeoIP
|
||||||
import re
|
import re
|
||||||
|
import maxminddb
|
||||||
|
|
||||||
srid = 4326
|
srid = 4326
|
||||||
re_point = re.compile(r'^POINT\((.+) (.+)\)$')
|
re_point = re.compile(r'^POINT\((.+) (.+)\)$')
|
||||||
|
@ -24,6 +25,7 @@ login_manager = flask_login.LoginManager(app)
|
||||||
login_manager.login_view = 'login_route'
|
login_manager.login_view = 'login_route'
|
||||||
osm_api_base = 'https://api.openstreetmap.org/api/0.6'
|
osm_api_base = 'https://api.openstreetmap.org/api/0.6'
|
||||||
|
|
||||||
|
maxminddb_reader = maxminddb.open_database(app.config["GEOLITE2"])
|
||||||
|
|
||||||
DB_URL = "postgresql:///matcher"
|
DB_URL = "postgresql:///matcher"
|
||||||
database.init_db(DB_URL)
|
database.init_db(DB_URL)
|
||||||
|
@ -72,11 +74,14 @@ def check_for_tagged_qid(qid):
|
||||||
def geoip_user_record():
|
def geoip_user_record():
|
||||||
gi = GeoIP.open(app.config["GEOIP_DATA"], GeoIP.GEOIP_STANDARD)
|
gi = GeoIP.open(app.config["GEOIP_DATA"], GeoIP.GEOIP_STANDARD)
|
||||||
|
|
||||||
remote_ip = request.remote_addr
|
remote_ip = request.get('ip', request.remote_addr)
|
||||||
return gi.record_by_addr(remote_ip)
|
return gi.record_by_addr(remote_ip)
|
||||||
|
|
||||||
|
|
||||||
def get_user_location():
|
def get_user_location():
|
||||||
|
remote_ip = request.args.get('ip', request.remote_addr)
|
||||||
|
return maxminddb_reader.get(remote_ip)["location"]
|
||||||
|
|
||||||
gir = geoip_user_record()
|
gir = geoip_user_record()
|
||||||
return (gir["latitude"], gir["longitude"]) if gir else None
|
return (gir["latitude"], gir["longitude"]) if gir else None
|
||||||
|
|
||||||
|
@ -167,14 +172,15 @@ def old_map_location(zoom, lat, lng):
|
||||||
|
|
||||||
@app.route("/map")
|
@app.route("/map")
|
||||||
def map_start_page():
|
def map_start_page():
|
||||||
location = get_user_location()
|
loc = get_user_location()
|
||||||
lat, lon = location
|
|
||||||
|
|
||||||
return redirect(url_for(
|
return redirect(url_for(
|
||||||
'map_location',
|
'map_location',
|
||||||
|
lat=f'{loc["latitude"]:.5f}',
|
||||||
|
lon=f'{loc["longitude"]:.5f}',
|
||||||
zoom=16,
|
zoom=16,
|
||||||
lat=f'{lat:.5f}',
|
radius=loc["accuracy_radius"],
|
||||||
lon=f'{lon:.5f}',
|
ip=request.args.get('ip'),
|
||||||
))
|
))
|
||||||
|
|
||||||
@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>")
|
||||||
|
@ -182,7 +188,14 @@ def map_location(zoom, lat, lon):
|
||||||
user = flask_login.current_user
|
user = flask_login.current_user
|
||||||
username = user.username if user.is_authenticated else None
|
username = user.username if user.is_authenticated else None
|
||||||
|
|
||||||
return render_template("map.html", zoom=zoom, lat=lat, lon=lon, username=username)
|
return render_template(
|
||||||
|
"map.html",
|
||||||
|
zoom=zoom,
|
||||||
|
lat=lat,
|
||||||
|
lon=lon,
|
||||||
|
radius=request.args.get('radius'),
|
||||||
|
username=username
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/old_map")
|
@app.route("/old_map")
|
||||||
|
|
Loading…
Reference in a new issue