Dans GeoDjango, par exemple, here Dans le formulaire, il semble qu'il existe une fonction qui peut acquérir des informations de la table DB (en utilisant PostGIS) et la faire fonctionner comme un serveur de tuiles vectorielles.
Je me suis demandé si je pouvais faire quelque chose de similaire avec le micro-framework Web de Python (ici j'utilise FastAPI), j'ai donc fait beaucoup de recherches et pris une note pour moi-même.
Idéalement, ce devrait être PostGIS, ou s'il s'agit de NoSQL, ce devrait être la [Geospatial Query] de MongoDB (https: // docs). Nous avons construit un système en utilisant autour de .mongodb.com / manual / geospatial-queries /), et le format de distribution n'est pas seulement GeoJSON mais aussi un format de tuile vectorielle binaire plus pratique ([Référence](https: //docs.mapbox.). Je voudrais prendre en charge com / vector-tiles / specification /)), mais il est difficile de tous les supporter soudainement, alors ici
GET
) au format <url> / {z} / {x} / {y} .geojson
et renvoyer une réponse (GeoJSON ou 404) au format appropriéL'accent principal est mis sur. D'autre part
--Connexion / liaison avec DB (PostgreSQL, MongoDB, etc.)
--Distribution au format binaire tel que mvt (pbf)
Cela n'est pas fait à ce stade et sera un problème pour l'avenir. (En d'autres termes, ** la situation est encore loin d'être pratique ** ... si possible, nous prévoyons d'enquêter et d'ajouter des articles à l'avenir)
démo
pyenv
+ miniconda3
--Utilisez l'environnement virtuel condaJe me souviens que les dépendances et les constructions des bibliothèques géospatiales étaient inopinément gênantes, alors je suis en train de créer un environnement à l'aide de conda.
L'environnement virtuel conda est, par exemple
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
Préparez un fichier YAML comme
#Créer un environnement virtuel à partir du fichier YAML
conda env create -f conda_packages.yml
#Activez l'environnement virtuel
conda activate <Nom de l'environnement virtuel>
Faire.
Le nom de l'environnement virtuel (name:
part) et la bibliothèque à installer (dependencies:
part) du fichier YAML doivent être modifiés si nécessaire.
Par exemple:
.
├── app
│ ├── __init__.py
│ ├── client
│ │ └── index.html
│ └── main.py
└── data
└── test.geojson
--ʻApp / main.py` implémente le traitement du serveur de tuiles
__init __. Py
est un fichier videdata / test.geojson
comme données de testDes exemples de ʻapp / main.py et ʻapp / client / index.html
sont détaillés ci-dessous.
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
) sont lues en spécifiant le chemin avec les géopandas.def test_tile_geojson
est le corps principal du processus, et {z} / {x} / {y}
(zoom, position) est Path Parameter Recevoir en tant que -params /) et sous la forme de Paramètre de requête s'il existe d'autres paramètres obligatoires (? = <*** après l'URL Vous pouvez le recevoir sous la forme>
) (Ici, la valeur de zoom minimum limit_zmin
est définie pour le test)
--Convertissez le {z} / {x} / {y}
reçu (chaque valeur entière) en une plage rectangulaire de latitude et de longitude (bbox), et découpez-la des données de test (test.geojson
) dans la plage de bbox ( intersection) Renvoie sous la forme de geojson. S'il n'y a pas de données, 404 est lancé (Référence).tile_coord
, la plupart des traitements décrits dans openstreetmap.org wiki sont tels quels.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'>Tuile Institut de géographie (carte standard)</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'>Tuile Institut géographique (carte des couleurs claires)</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'>Carrelage Institut de géographie (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'>Tuile Institut de géographie (carte blanche)</a>",
maxNativeZoom: 14,
maxZoom: 22,
opacity:1,
});
L.control.scale({
imperial: false,
metric: true,
}).addTo(map);
const baseLayers ={
"Tuile Institut de géographie (carte standard)": gsi_std,
"Tuile Institut géographique (carte des couleurs claires)": gsi_pale,
"Carrelage Institut de géographie (Ortho)": gsi_ort,
"Tuile Institut de géographie (carte blanche)": 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>
Dans la partie const tile_geojson_sample
, spécifiez l'URL du serveur de tuiles que vous avez créé et exécutez + la chaîne de requête et lisez-la.
L'essentiel est de vérifier si le serveur de tuiles fonctionne correctement, et comme ce n'est pas la partie essentielle, d'autres détails sont omis.
Pour créer le HTML ci-dessus,
Je l'ai mentionné.
Pour démarrer le serveur, par exemple, tapez la commande suivante:
uvicorn app.main:app
Vous pouvez vérifier l'API du serveur de tuiles créé en tant que Swagger (Open API) à partir, par exemple, de http: // localhost: 8000. (Génération automatique de documents + Vous pouvez facilement vérifier l'opération sur place)
Si vous lancez localement, vous pouvez vérifier le HTML de visualisation créé ci-dessus en ouvrant http: // localhost: 8000 / client etc.:
L'exemple d'exécution ci-dessus utilise les données de limite de ville / quartier / ville / village (Polygon, MultiPolygon), mais il peut être exécuté presque de la même manière en utilisant LineString
ou Point
comme données.
À propos, la partie qui manque étrangement est le problème du contenu des données de test. (Lors du traitement des intersections avec les géopandas, la sélection des données, etc. est effectuée à l'avance, par exemple en supprimant celles «non valides»)
Si vous regardez le journal de la console FastAPI (uvicorn) au moment de l'exécution,
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
C'est comme ressentir.
La plupart des données et traitements utilisés sont provisoires, mais j'ai pu construire un serveur de tuiles qui fonctionne comme ça.
Comme mentionné au début, il y a en fait de nombreux problèmes, mais pour le moment, c'était une bonne étude des tuiles de carte, des tuiles vectorielles et du micro-framework Web de Python en général.
Globalement assez utile
{z} / {x} / {y}
sur la carte, il est également pratique pour le débogage d'API, etc.GeoJSON
protobuf
Ceux avec l'extension «.mvt» ou «.pbf». Non implémenté cette fois, mais comme problème ultérieur
Une fonction différente de "Dynamic Vector Tile Server" mais utile
pbf / mvt
)
--Convertissez les fichiers GeoJSON etc. vers la source et créez des fichiers de répertoire comme / path / to / {z} / {x} / {y} .pbf
geobuf
Étant donné que les serveurs de tuiles dynamiques peuvent avoir des problèmes de charge et de temps de réponse, dans certains cas, il est tout à fait possible que cette méthode soit plus réaliste que la tuile.
Recommended Posts