forked from edward/owl-map
Show images in popups
This commit is contained in:
parent
c4aa27e8f4
commit
e3cefcfcbd
47
matcher/commons.py
Normal file
47
matcher/commons.py
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
import requests
|
||||||
|
import urllib.parse
|
||||||
|
from . import utils
|
||||||
|
|
||||||
|
commons_start = "http://commons.wikimedia.org/wiki/Special:FilePath/"
|
||||||
|
commons_url = "https://www.wikidata.org/w/api.php"
|
||||||
|
page_size = 50
|
||||||
|
|
||||||
|
|
||||||
|
def commons_uri_to_filename(uri):
|
||||||
|
return urllib.parse.unquote(utils.drop_start(uri, commons_start))
|
||||||
|
|
||||||
|
|
||||||
|
def api_call(params):
|
||||||
|
call_params = {
|
||||||
|
"format": "json",
|
||||||
|
"formatversion": 2,
|
||||||
|
**params,
|
||||||
|
}
|
||||||
|
|
||||||
|
return requests.get(commons_url, params=call_params, timeout=5)
|
||||||
|
|
||||||
|
|
||||||
|
def image_detail(filenames, thumbheight=None, thumbwidth=None):
|
||||||
|
params = {
|
||||||
|
"action": "query",
|
||||||
|
"prop": "imageinfo",
|
||||||
|
"iiprop": "url",
|
||||||
|
}
|
||||||
|
if thumbheight is not None:
|
||||||
|
params["iiurlheight"] = thumbheight
|
||||||
|
if thumbwidth is not None:
|
||||||
|
params["iiurlwidth"] = thumbwidth
|
||||||
|
|
||||||
|
images = {}
|
||||||
|
|
||||||
|
for cur in utils.chunk(filenames, page_size):
|
||||||
|
call_params = params.copy()
|
||||||
|
call_params["titles"] = "|".join(f"File:{f}" for f in cur)
|
||||||
|
|
||||||
|
r = api_call(call_params)
|
||||||
|
|
||||||
|
for image in r.json()["query"]["pages"]:
|
||||||
|
filename = utils.drop_start(image["title"], "File:")
|
||||||
|
images[filename] = image["imageinfo"][0] if "imageinfo" in image else None
|
||||||
|
|
||||||
|
return images
|
170
matcher/utils.py
Normal file
170
matcher/utils.py
Normal file
|
@ -0,0 +1,170 @@
|
||||||
|
from flask import current_app, request
|
||||||
|
from itertools import islice
|
||||||
|
import os.path
|
||||||
|
import json
|
||||||
|
import math
|
||||||
|
import user_agents
|
||||||
|
import re
|
||||||
|
import pattern.en
|
||||||
|
|
||||||
|
metres_per_mile = 1609.344
|
||||||
|
feet_per_metre = 3.28084
|
||||||
|
feet_per_mile = 5280
|
||||||
|
|
||||||
|
|
||||||
|
def chunk(it, size):
|
||||||
|
it = iter(it)
|
||||||
|
return iter(lambda: tuple(islice(it, size)), ())
|
||||||
|
|
||||||
|
|
||||||
|
def flatten(l):
|
||||||
|
return [item for sublist in l for item in sublist]
|
||||||
|
|
||||||
|
|
||||||
|
def drop_start(s, start):
|
||||||
|
assert s.startswith(start)
|
||||||
|
return s[len(start) :]
|
||||||
|
|
||||||
|
|
||||||
|
def remove_start(s, start):
|
||||||
|
return s[len(start) :] if s.startswith(start) else s
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_url(url):
|
||||||
|
for start in "http://", "https://", "www.":
|
||||||
|
url = remove_start(url, start)
|
||||||
|
return url.rstrip("/")
|
||||||
|
|
||||||
|
|
||||||
|
def contains_digit(s):
|
||||||
|
return any(c.isdigit() for c in s)
|
||||||
|
|
||||||
|
|
||||||
|
def cache_dir():
|
||||||
|
return current_app.config["CACHE_DIR"]
|
||||||
|
|
||||||
|
|
||||||
|
def cache_filename(filename):
|
||||||
|
return os.path.join(cache_dir(), filename)
|
||||||
|
|
||||||
|
|
||||||
|
def load_from_cache(filename):
|
||||||
|
return json.load(open(cache_filename(filename)))
|
||||||
|
|
||||||
|
|
||||||
|
def get_radius(default=1000):
|
||||||
|
arg_radius = request.args.get("radius")
|
||||||
|
return int(arg_radius) if arg_radius and arg_radius.isdigit() else default
|
||||||
|
|
||||||
|
|
||||||
|
def get_int_arg(name):
|
||||||
|
if name in request.args and request.args[name].isdigit():
|
||||||
|
return int(request.args[name])
|
||||||
|
|
||||||
|
|
||||||
|
def calc_chunk_size(area_in_sq_km, size=22):
|
||||||
|
side = math.sqrt(area_in_sq_km)
|
||||||
|
return max(1, math.ceil(side / size))
|
||||||
|
|
||||||
|
|
||||||
|
def file_missing_or_empty(filename):
|
||||||
|
return os.path.exists(filename) or os.stat(filename).st_size == 0
|
||||||
|
|
||||||
|
|
||||||
|
def is_bot():
|
||||||
|
""" Is the current request from a web robot? """
|
||||||
|
ua = request.headers.get("User-Agent")
|
||||||
|
return ua and user_agents.parse(ua).is_bot
|
||||||
|
|
||||||
|
|
||||||
|
def log_location():
|
||||||
|
return current_app.config["LOG_DIR"]
|
||||||
|
|
||||||
|
|
||||||
|
def good_location():
|
||||||
|
return os.path.join(log_location(), "complete")
|
||||||
|
|
||||||
|
|
||||||
|
def capfirst(value):
|
||||||
|
""" Uppercase first letter of string, leave rest as is. """
|
||||||
|
return value[0].upper() + value[1:] if value else value
|
||||||
|
|
||||||
|
|
||||||
|
def any_upper(value):
|
||||||
|
return any(c.isupper() for c in value)
|
||||||
|
|
||||||
|
|
||||||
|
def find_log_file(place):
|
||||||
|
start = f"{place.place_id}_"
|
||||||
|
for f in os.scandir(good_location()):
|
||||||
|
if f.name.startswith(start):
|
||||||
|
return f.path
|
||||||
|
|
||||||
|
|
||||||
|
def get_free_space(config):
|
||||||
|
s = os.statvfs(config["FREE_SPACE_PATH"])
|
||||||
|
return s.f_bsize * s.f_bavail
|
||||||
|
|
||||||
|
|
||||||
|
def display_distance(units, dist):
|
||||||
|
if units in ("miles_and_feet", "miles_and_yards"):
|
||||||
|
total_feet = dist * feet_per_metre
|
||||||
|
miles = total_feet / feet_per_mile
|
||||||
|
|
||||||
|
if miles > 0.5:
|
||||||
|
return f"{miles:,.2f} miles"
|
||||||
|
else:
|
||||||
|
return {
|
||||||
|
"miles_and_feet": f"{total_feet:,.0f} feet",
|
||||||
|
"miles_and_yards": f"{total_feet / 3:,.0f} yards",
|
||||||
|
}[units]
|
||||||
|
|
||||||
|
if units == "miles_and_metres":
|
||||||
|
miles = dist / metres_per_mile
|
||||||
|
return f"{miles:,.2f} miles" if miles > 0.5 else f"{dist:,.0f} metres"
|
||||||
|
|
||||||
|
if units == "km_and_metres":
|
||||||
|
units = "km" if dist > 500 else "metres"
|
||||||
|
if units == "metres":
|
||||||
|
return f"{dist:,.0f} m"
|
||||||
|
if units == "km":
|
||||||
|
return f"{dist / 1000:,.2f} km"
|
||||||
|
|
||||||
|
|
||||||
|
re_range = re.compile(r"\b(\d+) ?(?:to|-) ?(\d+)\b", re.I)
|
||||||
|
re_number_list = re.compile(r"\b([\d, ]+) (?:and|&) (\d+)\b", re.I)
|
||||||
|
re_number = re.compile(r"^(?:No\.?|Number)? ?(\d+)\b")
|
||||||
|
|
||||||
|
|
||||||
|
def is_in_range(address_range, address):
|
||||||
|
m_number = re_number.match(address)
|
||||||
|
if not m_number:
|
||||||
|
return False
|
||||||
|
|
||||||
|
m_range = re_range.search(address_range)
|
||||||
|
if m_range:
|
||||||
|
start, end = int(m_range.group(1)), int(m_range.group(2))
|
||||||
|
if re_range.search(address):
|
||||||
|
return False
|
||||||
|
return start <= int(m_number.group(1)) <= end
|
||||||
|
|
||||||
|
m_list = re_number_list.search(address_range)
|
||||||
|
if m_list:
|
||||||
|
numbers = {n.strip() for n in m_list.group(1).split(",")} | {m_list.group(2)}
|
||||||
|
if re_number_list.search(address):
|
||||||
|
return False
|
||||||
|
return m_number.group(1) in numbers
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def pluralize_label(label):
|
||||||
|
text = label["value"]
|
||||||
|
if label["language"] != "en":
|
||||||
|
return text
|
||||||
|
|
||||||
|
# pattern.en.pluralize has the plural of 'mine' as 'ours'
|
||||||
|
if text == "mine":
|
||||||
|
return "mines"
|
||||||
|
|
||||||
|
return pattern.en.pluralize(text)
|
|
@ -297,6 +297,9 @@ function load_wikidata_items() {
|
||||||
popup += `<br><a href="${isa_url}">${isa_label}</a> (${isa_qid})`;
|
popup += `<br><a href="${isa_url}">${isa_label}</a> (${isa_qid})`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (item.image_list && item.image_list.length) {
|
||||||
|
popup += `<br><img src="/commons/${item.image_list[0]}">`;
|
||||||
|
}
|
||||||
popup += '</p>';
|
popup += '</p>';
|
||||||
marker.bindPopup(popup);
|
marker.bindPopup(popup);
|
||||||
marker.addTo(group);
|
marker.addTo(group);
|
||||||
|
|
11
web_view.py
11
web_view.py
|
@ -3,7 +3,7 @@
|
||||||
from flask import Flask, render_template, request, jsonify, redirect, url_for
|
from flask import Flask, render_template, request, jsonify, redirect, url_for
|
||||||
from sqlalchemy import func
|
from sqlalchemy import func
|
||||||
from sqlalchemy.orm import selectinload
|
from sqlalchemy.orm import selectinload
|
||||||
from matcher import nominatim, model, database
|
from matcher import nominatim, model, database, commons
|
||||||
from collections import Counter
|
from collections import Counter
|
||||||
from time import time
|
from time import time
|
||||||
import GeoIP
|
import GeoIP
|
||||||
|
@ -162,11 +162,13 @@ def get_markers(all_items):
|
||||||
if "en" not in item.labels:
|
if "en" not in item.labels:
|
||||||
continue
|
continue
|
||||||
locations = [list(i.get_lat_lon()) for i in item.locations]
|
locations = [list(i.get_lat_lon()) for i in item.locations]
|
||||||
|
image_filenames = item.get_claim("P18")
|
||||||
item = {
|
item = {
|
||||||
"qid": item.qid,
|
"qid": item.qid,
|
||||||
"label": item.label(),
|
"label": item.label(),
|
||||||
"description": item.description(),
|
"description": item.description(),
|
||||||
"markers": locations,
|
"markers": locations,
|
||||||
|
"image_list": image_filenames,
|
||||||
"isa_list": [v["id"] for v in item.get_claim("P31")],
|
"isa_list": [v["id"] for v in item.get_claim("P31")],
|
||||||
}
|
}
|
||||||
items.append(item)
|
items.append(item)
|
||||||
|
@ -200,6 +202,13 @@ def identifier_index():
|
||||||
return render_template("identifier_index.html", property_map=property_map)
|
return render_template("identifier_index.html", property_map=property_map)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/commons/<filename>")
|
||||||
|
def get_commons_image(filename):
|
||||||
|
detail = commons.image_detail([filename], thumbheight=250, thumbwidth=250)
|
||||||
|
image = detail[filename]
|
||||||
|
return redirect(image["thumburl"])
|
||||||
|
|
||||||
|
|
||||||
@app.route("/identifier/<pid>")
|
@app.route("/identifier/<pid>")
|
||||||
def identifier_page(pid):
|
def identifier_page(pid):
|
||||||
per_page = 10
|
per_page = 10
|
||||||
|
|
Loading…
Reference in a new issue