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