In GeoDjango zum Beispiel hier In dem Formular scheint es eine Funktion zu geben, die Informationen aus der DB-Tabelle (mit PostGIS) abrufen und als Vektor-Kachelserver betreiben kann.
Ich habe mich gefragt, ob ich mit Pythons Micro-Web-Framework (hier verwende ich FastAPI) etwas Ähnliches tun könnte, also habe ich viel recherchiert und mir eine Notiz gemacht.
Idealerweise sollte es [PostGIS] sein (https://postgis.net/docs/manual-2.4/postgis-ja.html), oder wenn es NoSQL ist, sollte es MongoDBs [Geospatial Query](https: // docs) sein. Wir haben ein System mit .mongodb.com / manual / geospatial-queries /) erstellt. Das Verteilungsformat ist nicht nur GeoJSON, sondern auch ein praktischeres Format für binäre Vektorkacheln ([Referenz](https: //docs.mapbox.). Ich würde gerne com / vector-tiles / specification /)) unterstützen, aber es ist schwierig, alle plötzlich zu unterstützen, also hier
GET
) im Format <url> / {z} / {x} / {y} .geojson
und geben Sie eine Antwort (GeoJSON oder 404) im entsprechenden Format zurückDas Hauptaugenmerk liegt auf. Auf der anderen Seite
--Verbindung / Verknüpfung mit DB (PostgreSQL, MongoDB usw.)
Dies ist derzeit noch nicht geschehen und wird ein Problem für die Zukunft sein. (Mit anderen Worten, ** die Situation ist noch weit vom praktischen Nutzen entfernt ** ... Wenn möglich, planen wir, Artikel in Zukunft zu untersuchen und hinzuzufügen)
Demo
pyenv
+ miniconda3
--Verwenden Sie die virtuelle Umgebung von condaIch erinnere mich, dass die Abhängigkeiten und Builds von Geodatenbibliotheken unerwartet problematisch waren. Deshalb baue ich hier die Umgebung mit conda.
Die conda virtuelle Umgebung ist zum Beispiel
conda_packages.yml
name: fastapi_geojsontileserver
channels:
- conda-forge
- defaults
dependencies:
# core
- python==3.7.*
# FastAPI
- fastapi
- uvicorn
- aiofiles
# for handling GeoSpatial data
- pandas>=1.1
- geopandas>=0.8
- gdal==3.0.*
- shapely
Bereiten Sie eine YAML-Datei wie vor
#Erstellen Sie eine virtuelle Umgebung aus einer YAML-Datei
conda env create -f conda_packages.yml
#Aktivieren Sie die virtuelle Umgebung
conda activate <Name der virtuellen Umgebung>
Machen.
Der Name der virtuellen Umgebung (name:
part) und die zu installierende Bibliothek (dependencies:
part) der YAML-Datei sollten nach Bedarf bearbeitet werden.
Zum Beispiel:
.
├── app
│ ├── __init__.py
│ ├── client
│ │ └── index.html
│ └── main.py
└── data
└── test.geojson
app / main.py
data / test.geojson
als Testdaten einBeispiele für "app / main.py" und "app / client / index.html" sind unten aufgeführt.
app/main.py
main.py
"""
app main
GeoJSON VectorTileLayer Test
"""
import pathlib
import json
import math
from typing import Optional
from fastapi import (
FastAPI,
HTTPException,
)
from fastapi.staticfiles import StaticFiles
from fastapi.responses import RedirectResponse, HTMLResponse
import geopandas as gpd
import shapely.geometry
# const
PATH_STATIC = str(pathlib.Path(__file__).resolve().parent / "client")
EXT_DATA_PATH = "./data/" # TMP
# test data
gdf_testdata = gpd.read_file(EXT_DATA_PATH + "test.geojson")
def create_app():
"""create app"""
_app = FastAPI()
# static
_app.mount(
"/client",
StaticFiles(directory=PATH_STATIC, html=True),
name="client",
)
return _app
app = create_app()
@app.get('/', response_class=HTMLResponse)
async def site_root():
"""root"""
return RedirectResponse("/client")
@app.get("/tiles/test/{z}/{x}/{y}.geojson")
async def test_tile_geojson(
z: int,
x: int,
y: int,
limit_zmin: Optional[int] = 8,
) -> dict:
"""
return GeoJSON Tile
"""
if limit_zmin is not None:
if z < limit_zmin:
raise HTTPException(status_code=404, detail="Over limit of zoom")
# test data
gdf = gdf_testdata.copy()
bbox_polygon = tile_bbox_polygon(z, x, y)
# filtering
intersections = gdf.geometry.intersection(bbox_polygon)
gs_filtered = intersections[~intersections.is_empty] # geoseries
gdf_filtered = gpd.GeoDataFrame(
gdf.loc[gs_filtered.index, :].drop(columns=['geometry']),
geometry=gs_filtered,
)
# NO DATA
if len(gs_filtered) == 0:
raise HTTPException(status_code=404, detail="No Data")
# return geojson
return json.loads(
gdf_filtered.to_json()
)
def tile_coord(
zoom: int,
xtile: int,
ytile: int,
) -> (float, float):
"""
This returns the NW-corner of the square. Use the function
with xtile+1 and/or ytile+1 to get the other corners.
With xtile+0.5 & ytile+0.5 it will return the center of the tile.
http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#Tile_numbers_to_lon..2Flat._2
"""
n = 2.0 ** zoom
lon_deg = xtile / n * 360.0 - 180.0
lat_rad = math.atan(math.sinh(math.pi * (1 - 2 * ytile / n)))
lat_deg = math.degrees(lat_rad)
return (lon_deg, lat_deg)
def tile_bbox_polygon(
zoom: int,
xtile: int,
ytile: int,
) -> shapely.geometry.Polygon:
"""
create bbox for Tile by using shapely.geometry
"""
z = zoom
x = xtile
y = ytile
# get bbox
nw = tile_coord(z, x, y)
se = tile_coord(z, x+1, y+1)
bbox = shapely.geometry.Polygon(
[
nw, (se[0], nw[1]),
se, (nw[0], se[1]), nw
]
)
return bbox
test.geojson
) werden gelesen, indem der Pfad mit Geopandas angegeben wird.def test_tile_geojson
ist der Hauptteil des Prozesses und {z} / {x} / {y}
(Zoom, Position) ist Pfadparameter Erhalten Sie als -params /) und in Form von Abfrageparameter, wenn andere erforderliche Parameter vorhanden sind (? = <*** nach URL Sie können es in Form von>
) erhalten (hier wird der minimale Zoomwert limit_zmin
zum Testen eingestellt){z} / {x} / {y}
(jeder ganzzahlige Wert) in einen rechteckigen Längen- und Breitengrad (bbox) und schneiden Sie es aus den Testdaten (test.geojson
) im Bereich von bbox ( Kreuzung) Gibt in Form von Geojson zurück. Wenn keine Daten vorhanden sind, wird 404 ausgelöst (Referenz).tile_coord
ist der größte Teil der in openstreetmap.org wiki beschriebenen Verarbeitung unverändert.app/client/index.html
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0" />
<title>Leaflet GeoJSON TileLayer(Polygon) Test</title>
<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/leaflet.css" />
<script src="https://unpkg.com/[email protected]/dist/leaflet.js"></script>
<script src="https://unpkg.com/[email protected]/leaflet-hash.js"></script>
<style>
#map {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
}
.leaflet-control-container::after {
content: url(https://maps.gsi.go.jp/image/map/crosshairs.png);
z-index: 1000;
display: block;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
</style>
</head>
<body>
<div id="map"></div>
<script>
// Initalize map
const map = L.map("map", L.extend({
minZoom: 5,
zoom: 14,
maxZoom: 22,
center: [35.5, 139.5],
}, L.Hash.parseHash(location.hash)));
map.zoomControl.setPosition("bottomright");
L.hash(map);
// GeoJSON VectorTileLayer
const tile_geojson_sample = Object.assign(new L.GridLayer({
attribution: "hoge",
minZoom: 4,
maxZoom: 22,
}), {
createTile: function(coords) {
const template = "http://localhost:8000/tiles/test/{z}/{x}/{y}.geojson?limit_zmin=7";
const div = document.createElement('div');
div.group = L.layerGroup();
fetch(L.Util.template(template, coords)).then(a => a.ok ? a.json() : null).then(geojson => {
if (!div.group) return;
if (!this._map) return;
if (!geojson) return;
div.group.addLayer(L.geoJSON(geojson, {
style: () => {
return {}
}
}).bindPopup("test"));
div.group.addTo(this._map);
});
return div;
}
}).on("tileunload", function(e) {
if (e.tile.group) {
if (this._map) this._map.removeLayer(e.tile.group);
delete e.tile.group;
}
});
// basemap layers
const osm = L.tileLayer('http://tile.openstreetmap.jp/{z}/{x}/{y}.png', {
attribution: "<a href='http://osm.org/copyright' target='_blank'>OpenStreetMap</a> contributors",
// minZoom: 10,
maxNativeZoom: 18,
maxZoom: 22,
});
const gsi_std = L.tileLayer(
'https://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png',
{
attribution: "<a href='http://portal.cyberjapan.jp/help/termsofuse.html' target='_blank'>Kachel des Geographie-Instituts (Standardkarte)</a>",
maxNativeZoom: 18,
maxZoom: 22,
opacity:1
});
const gsi_pale = L.tileLayer(
'http://cyberjapandata.gsi.go.jp/xyz/pale/{z}/{x}/{y}.png',
{
attribution: "<a href='http://portal.cyberjapan.jp/help/termsofuse.html' target='_blank'>Geografische Institutskachel (helle Farbkarte)</a>",
maxNativeZoom: 18,
maxZoom: 22,
});
const gsi_ort = L.tileLayer(
'https://cyberjapandata.gsi.go.jp/xyz/ort/{z}/{x}/{y}.jpg',
{
attribution: "<a href='http://portal.cyberjapan.jp/help/termsofuse.html' target='_blank'>Geographie Institut Fliese (Ortho)</a>",
maxNativeZoom: 17,
maxZoom: 22,
opacity:0.9
});
const gsi_blank = L.tileLayer(
'https://cyberjapandata.gsi.go.jp/xyz/blank/{z}/{x}/{y}.png',
{
attribution: "<a href='http://portal.cyberjapan.jp/help/termsofuse.html' target='_blank'>Geography Institute Kachel (weiße Karte)</a>",
maxNativeZoom: 14,
maxZoom: 22,
opacity:1,
});
L.control.scale({
imperial: false,
metric: true,
}).addTo(map);
const baseLayers ={
"Kachel des Geographie-Instituts (Standardkarte)": gsi_std,
"Geografische Institutskachel (helle Farbkarte)": gsi_pale,
"Geographie Institut Fliese (Ortho)": gsi_ort,
"Geography Institute Kachel (weiße Karte)": gsi_blank,
'osm': osm.addTo(map),
};
const overlays = {"GeoJSON TileLayer(sample)": tile_geojson_sample};
L.control.layers(baseLayers, overlays, {position:'topright',collapsed:true}).addTo(map);
const hash = L.hash(map);
</script>
</body>
</html>
Geben Sie im Teil const tile_geojson_sample
die URL des von Ihnen erstellten Kachelservers an, führen Sie + die Abfragezeichenfolge aus und lesen Sie sie.
Die Hauptsache ist, zu überprüfen, ob der Kachelserver ordnungsgemäß funktioniert. Da dies nicht der wesentliche Teil ist, werden weitere Details weggelassen.
Um den obigen HTML-Code zu erstellen,
Ich bezog mich auf.
Geben Sie zum Starten des Servers beispielsweise den folgenden Befehl ein:
uvicorn app.main:app
Die API des erstellten Kachelservers kann als Swagger (Open API) beispielsweise von http: // localhost: 8000 bestätigt werden. (Automatische Dokumentenerstellung + Sie können den Vorgang einfach vor Ort überprüfen.)
Wenn Sie lokal starten, können Sie den oben erstellten Visualisierungs-HTML-Code überprüfen, indem Sie http: // localhost: 8000 / client etc öffnen.
Das obige Ausführungsbeispiel verwendet Grenzdaten für Stadt / Gemeinde / Stadt / Dorf (Polygon, MultiPolygon), kann jedoch auf fast dieselbe Weise ausgeführt werden, indem "LineString" oder "Point" als Daten verwendet werden. Der Teil, der seltsamerweise fehlt, ist übrigens das Problem des Inhalts der Testdaten. (Bei der Kreuzungsverarbeitung mit Geopandas werden Daten im Voraus ausgewählt, z. B. "ungültige" Daten entfernen.)
Wenn Sie sich das FastAPI-Konsolenprotokoll (uvicorn) zur Laufzeit ansehen,
INFO: 127.0.0.1:54910 - "GET /tiles/test/9/451/202.geojson?limit_zmin=7 HTTP/1.1" 200 OK
INFO: 127.0.0.1:54902 - "GET /tiles/test/9/456/202.geojson?limit_zmin=7 HTTP/1.1" 404 Not Found
INFO: 127.0.0.1:54908 - "GET /tiles/test/9/450/199.geojson?limit_zmin=7 HTTP/1.1" 200 OK
INFO: 127.0.0.1:54910 - "GET /tiles/test/9/457/199.geojson?limit_zmin=7 HTTP/1.1" 404 Not Found
INFO: 127.0.0.1:54896 - "GET /tiles/test/9/457/200.geojson?limit_zmin=7 HTTP/1.1" 404 Not Found
INFO: 127.0.0.1:54904 - "GET /tiles/test/9/450/201.geojson?limit_zmin=7 HTTP/1.1" 200 OK
INFO: 127.0.0.1:54906 - "GET /tiles/test/9/457/201.geojson?limit_zmin=7 HTTP/1.1" 404 Not Found
INFO: 127.0.0.1:54908 - "GET /tiles/test/9/457/202.geojson?limit_zmin=7 HTTP/1.1" 404 Not Found
INFO: 127.0.0.1:54904 - "GET /tiles/test/9/451/198.geojson?limit_zmin=7 HTTP/1.1" 200 OK
INFO: 127.0.0.1:54902 - "GET /tiles/test/9/450/202.geojson?limit_zmin=7 HTTP/1.1" 200 OK
INFO: 127.0.0.1:54896 - "GET /tiles/test/9/452/198.geojson?limit_zmin=7 HTTP/1.1" 200 OK
INFO: 127.0.0.1:54910 - "GET /tiles/test/9/450/198.geojson?limit_zmin=7 HTTP/1.1" 200 OK
INFO: 127.0.0.1:54906 - "GET /tiles/test/9/453/198.geojson?limit_zmin=7 HTTP/1.1" 200 OK
INFO: 127.0.0.1:54910 - "GET /tiles/test/9/454/198.geojson?limit_zmin=7 HTTP/1.1" 200 OK
INFO: 127.0.0.1:54902 - "GET /tiles/test/9/449/199.geojson?limit_zmin=7 HTTP/1.1" 404 Not Found
INFO: 127.0.0.1:54904 - "GET /tiles/test/9/449/200.geojson?limit_zmin=7 HTTP/1.1" 200 OK
INFO: 127.0.0.1:54908 - "GET /tiles/test/9/449/198.geojson?limit_zmin=7 HTTP/1.1" 404 Not Found
INFO: 127.0.0.1:54910 - "GET /tiles/test/9/448/200.geojson?limit_zmin=7 HTTP/1.1" 404 Not Found
INFO: 127.0.0.1:54906 - "GET /tiles/test/9/449/201.geojson?limit_zmin=7 HTTP/1.1" 200 OK
INFO: 127.0.0.1:54896 - "GET /tiles/test/9/448/199.geojson?limit_zmin=7 HTTP/1.1" 404 Not Found
INFO: 127.0.0.1:54906 - "GET /tiles/test/7/112/49.geojson?limit_zmin=7 HTTP/1.1" 200 OK
INFO: 127.0.0.1:54908 - "GET /tiles/test/9/448/198.geojson?limit_zmin=7 HTTP/1.1" 404 Not Found
INFO: 127.0.0.1:54902 - "GET /tiles/test/9/455/198.geojson?limit_zmin=7 HTTP/1.1" 200 OK
INFO: 127.0.0.1:54904 - "GET /tiles/test/9/448/201.geojson?limit_zmin=7 HTTP/1.1" 200 OK
INFO: 127.0.0.1:54906 - "GET /tiles/test/7/111/49.geojson?limit_zmin=7 HTTP/1.1" 404 Not Found
INFO: 127.0.0.1:54902 - "GET /tiles/test/7/114/49.geojson?limit_zmin=7 HTTP/1.1" 200 OK
INFO: 127.0.0.1:54896 - "GET /tiles/test/7/113/49.geojson?limit_zmin=7 HTTP/1.1" 200 OK
INFO: 127.0.0.1:54910 - "GET /tiles/test/7/113/48.geojson?limit_zmin=7 HTTP/1.1" 200 OK
INFO: 127.0.0.1:54902 - "GET /tiles/test/7/112/51.geojson?limit_zmin=7 HTTP/1.1" 200 OK
Es fühlt sich an wie.
Die meisten verwendeten Daten und Verarbeitungen sind vorläufig, aber ich konnte einen Kachelserver erstellen, der so funktioniert.
Wie eingangs erwähnt, gibt es tatsächlich viele Probleme, aber vorerst war es eine gute Studie über Kartenkacheln, Vektorkacheln und Pythons Micro-Web-Framework im Allgemeinen.
Insgesamt sehr hilfreich
GeoJSON
protobuf
Die mit der Erweiterung ".mvt" oder ".pbf". Diesmal nicht implementiert, aber als Folgeproblem
Eine andere Funktion als "Dynamic Vector Tile Server", aber hilfreich
pbf / mvt
)
--Konvertieren Sie GeoJSON-Dateien usw. in die Quelle und erstellen Sie eine Verzeichnisdateigruppe wie "/ path / to / {z} / {x} / {y} .pbf"geobuf
Da bei dynamischen Kachelservern Probleme mit der Last und der Antwortzeit auftreten können, ist diese Methode in einigen Fällen durchaus realistischer als gekachelt.
Recommended Posts