Browse Source

Servidor python amb enpoint de geometries; Actualització del client i separació de la lògica mapa i la lògca de carrega de dades;

develop
orzo 7 months ago
parent
commit
33c6acadee
  1. 2
      .gitignore
  2. BIN
      client/altures_bcn.pdf
  3. 47
      client/public/data/barris.4326.geojson
  4. 452489
      client/public/data/constru.geojson
  5. 17
      client/public/data/districtres.geojson
  6. 76396
      client/public/data/parceles.geojson
  7. 4
      client/public/index.css
  8. 43
      client/public/index.html
  9. 186
      client/public/index.js
  10. 22
      client/public/js/index.js
  11. 157
      client/public/js/map.js
  12. 74
      client/public/js/models/geometries.js
  13. 2
      client/public/vendor/vue.min.js
  14. BIN
      server/desregistradores.sqlite
  15. 45
      server/main.py
  16. 31
      server/public/index.html
  17. 91
      server/src/api/main.py
  18. 28
      server/static/index.html

2
.gitignore vendored

@ -5,4 +5,4 @@ images
node_modules
__pycache__
log/*
server/public/*
server/static/*

BIN
client/altures_bcn.pdf

Binary file not shown.

47
client/public/data/barris.4326.geojson

File diff suppressed because one or more lines are too long

452489
client/public/data/constru.geojson

File diff suppressed because one or more lines are too long

17
client/public/data/districtres.geojson

File diff suppressed because one or more lines are too long

76396
client/public/data/parceles.geojson

File diff suppressed because one or more lines are too long

4
client/public/index.css

@ -1,4 +1,4 @@
#myMap {
#map {
height: 1000px;
}
@ -10,4 +10,4 @@
top: 10px;
right: 10px;
font-size: 30px;
}
}

43
client/public/index.html

@ -1,31 +1,28 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css" integrity="sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A==" crossorigin="" />
<link rel="stylesheet" href="index.css">
<script src="/vendor/vue.min.js"></script>
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link
rel="stylesheet"
href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css"
integrity="sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A=="
crossorigin=""
/>
<link rel="stylesheet" href="/static/index.css" />
<script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"></script>
<script src="/static/vendor/vue.min.js"></script>
<title>Map</title>
</head>
</head>
<body>
<body>
<div id="app"></div>
</body>
<script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js" integrity="sha512-XQoYMqMTK8LvdxXYG3nZ448hOEQiglfqkJs1NOQV44cWnUrBc8PkAOcXy20w0vlaXaVUearIOBhiXZ5V3ynxwA==" crossorigin=""></script>
<script src="index.js"></script>
<script src="/static/js/models/geometries.js"></script>
<script src="/static/js/map.js"></script>
<script src="/static/js/index.js"></script>
</body>
</html>

186
client/public/index.js

@ -1,186 +0,0 @@
function renderLeaflet (el, onClick){
const tilesProvider = 'https://{s}.tile.openstreetmap.de/{z}/{x}/{y}.png';
let myMap = L.map(el).setView([41.41, 2.13], 12);
L.tileLayer(tilesProvider, {
maxzoom: 18,
}).addTo(myMap)
/*DEfinim les propietats del marcador*/
let iconMarker = L.icon({
iconUrl: 'images/marker.png',
iconSize: [30, 30],
iconAnchor: [15, 30] /*A quin punt en píxels de la imatge es clva el marcador al mapa */
})
let marker = L.marker([41.41, 2.13], {
icon: iconMarker
}).addTo(myMap);
/*Definim el marcador amb les coordenades i l'objecte iconMarker definit*/
/*carregar els arxius geojson*/
function fetchJSON (url) {
return fetch(url)
.then(function(response) {
return response.json();
});
}
function fetchParceles () {
return fetch("parceles", {
method: "POST",
cache: "no-cache",
cors: "no-cors",
headers: {
"Content-Type": "application/json",
"Accept": "application/json"
},
redirect: "error",
body: JSON.stringify({
west: myMap.getBounds().getWest(),
north: myMap.getBounds().getNorth(),
east: myMap.getBounds().getEast(),
south: myMap.getBounds().getSouth()
})
}).then(function(response) {
return response.json();
});
};
//canvis de color segons el valor d'una variable
function getColor(nom, districte) {
return nom == "Ciutat Vella" ? '#f7fcfd' :
nom == "Eixample" ? '#e5f5f9' :
nom == "Sants-Montjuïc" ? '#ccece6' :
nom == "Les Corts" ? '#99d8c9' :
nom == "Sarrià-Sant Gervasi" ? '#66c2a4' :
nom == "Gràcia" ? '#41ae76' :
nom == "Horta-Guinardó" ? '#238b45' :
nom == "Nou Barris" ? '#006d2c' :
nom == "Sant Andreu" ? '#00441b' :
nom == "Sant Martí" ? '#e0f3db' :
districte == "01" ? '#f7fcfd' :
districte == "02" ? '#e5f5f9' :
districte == "03" ? '#ccece6' :
districte == "04" ? '#99d8c9' :
districte == "05" ? '#66c2a4' :
districte== "06" ? '#41ae76' :
districte == "07" ? '#238b45' :
districte == "08" ? '#006d2c' :
districte == "09" ? '#00441b' :
districte == "10" ? '#e0f3db' :
'grey';
};
//creem una funció per donar estil segons el NOM que tingui el districte
function style(feature) {
return {
fillColor: getColor(feature.properties.NOM, feature.properties.DISTRICTE),
weight: 1.5,
opacity: 1,
color: 'black',
dashArray: '3',
fillOpacity: 0.7
};
}
var layers = {
districtes: null,
barris: null
}
var currentlayer = null;//DEclarem una variable fora de la funció fetch perquè sigui accessible
//Here we get access to the layer that was hovered through e.target, set a thick grey border on
//the layer as our highlight effect, also bringing it to the front so that the border doesn’t clash with nearby states (but not for IE, Opera or Edge, since they have problems doing bringToFront on mouseover).
//First we’ll define an event listener for layer mouseover event
function highlightFeature(e) {
var layer = e.target; //creem una varible layer on guardem el troçet de layer pel que passem per sobre
layer.setStyle({
weight: 5,
color: '#000000',
dashArray: '',
fillOpacity: 0.7
});
if (!L.Browser.ie && !L.Browser.opera && !L.Browser.edge) {
layer.bringToFront();
}
};
//Next we’ll define what happens on mouseout
function resetHighlight(e) {
currentlayer.resetStyle(e.target);
};
//As an additional touch, let’s define a click listener that zooms to the state:
function zoomToFeature(e) {
if (typeof onClick === 'function') {
onClick(e)
}
myMap.fitBounds(e.target.getBounds());
}
//Now we’ll use the onEachFeature option to add the listeners on our state layers:
function onEachFeature(feature, layer) {
layer.on({
mouseover: highlightFeature,
mouseout: resetHighlight,
click: zoomToFeature
});
};
fetchJSON('data/districtres.geojson').then(function(data) {
layers.districtes = data;
currentlayer = L.geoJSON(data, {
style: style,
onEachFeature: onEachFeature
}).addTo(myMap);
});
function loadData (ev) {
if (myMap.getZoom() <= 12) {
currentlayer.clearLayers();
currentlayer.addData(layers.districtes);
} else if (myMap.getZoom() > 12 && myMap.getZoom() <= 16) {
if (layers.barris != null) {
currentlayer.clearLayers();
currentlayer.addData(layers.barris);
} else {
fetchJSON('data/barris.4326.geojson')
.then(function(data) {
layers.barris = data;
currentlayer.clearLayers();
currentlayer.addData(data);
});
}
} else {
fetchParceles().then(function(data) {
currentlayer.clearLayers();
currentlayer.addData(data);
})
}
};
//creem un event que escolti els botons de zoom (es una fucnion propia del leaflet)
myMap.on("zoom", loadData);
myMap.on("moveend", loadData);
}
var MapInfo = Vue.component('map-info', {
template: '<div id="mapInfo">{{ message }}</div>',
props: ['message']
})
var myView = new Vue ({
el: "#app",
template: `<div id='app'>
<div id='myMap'></div>
<map-info v-bind:message='nomDistricte'></map-info>
</div>`,
data() {
return {
nomDistricte: null
}
},
mounted: function () {
renderLeaflet (this.$el, function (ev) {
myView.nomDistricte = ev.target.feature.properties.NOM
}) //aquí el this apunta a l'objecte dintre del qual s'ha definit la funció
}
})

22
client/public/js/index.js

@ -0,0 +1,22 @@
var MapInfo = Vue.component("map-info", {
template: '<div id="mapInfo">{{ message }}</div>',
props: ["message"],
});
var myView = new Vue({
el: "#app",
template: `<div id='app'>
<map-view v-on:info="onMapInfo"></map-view>
<map-info v-bind:message='nomDistricte'></map-info>
</div>`,
data() {
return {
nomDistricte: null,
};
},
methods: {
onMapInfo(event) {
this.nomDistricte = event.target.feature.propertie.nom;
},
},
});

157
client/public/js/map.js

@ -0,0 +1,157 @@
const MapView = Vue.component("map-view", {
template: '<div id="map"></div>',
data() {
return {
map: null,
tile: null,
layers: null,
model: new GeometriesModel(),
tileProvider: "https://{s}.tile.openstreetmap.de/{z}/{x}/{y}.png",
debouncedLoad: null,
};
},
mounted() {
this.map = L.map(this.$el).setView([41.41, 2.13], 12);
this.tile = L.tileLayer(this.tileProvider, {
maxzoom: 18,
}).addTo(this.map);
this.layers = L.geoJSON(
{
type: "FeatureCollection",
features: [],
},
{
style: this.style,
onEachFeature: this.onEachFeature,
}
).addTo(this.map);
this.map.on("zoom", this.load);
this.map.on("moveend", this.load);
this.load();
},
methods: {
load: function (event) {
clearTimeout(this.debouncedLoad);
const self = this;
this.debouncedLoad = setTimeout(function () {
if (self.map.getZoom() <= 12) {
self.model.load("districtes", self.boundingBox()).then((data) => {
self.layers.clearLayers();
self.layers.addData(data);
});
} else if (self.map.getZoom() > 12 && self.map.getZoom() <= 16) {
self.model.load("barris", self.boundingBox()).then((data) => {
self.layers.clearLayers();
self.layers.addData(data);
});
} else {
self.model.load("parceles", self.boundingBox()).then((data) => {
self.layers.clearLayers();
self.layers.addData(data);
});
}
}, 200);
},
boundingBox: function () {
const bounds = this.map.getBounds();
return {
west: Math.round(bounds.getWest() * 1e5) / 1e5,
north: Math.round(bounds.getNorth() * 1e5) / 1e5,
east: Math.round(bounds.getEast() * 1e5) / 1e5,
south: Math.round(bounds.getSouth() * 1e5) / 1e5,
};
},
getColor: function (nom, districte) {
//canvis de color segons el valor d'una variable
return nom == "Ciutat Vella"
? "#f7fcfd"
: nom == "Eixample"
? "#e5f5f9"
: nom == "Sants-Montjuïc"
? "#ccece6"
: nom == "Les Corts"
? "#99d8c9"
: nom == "Sarrià-Sant Gervasi"
? "#66c2a4"
: nom == "Gràcia"
? "#41ae76"
: nom == "Horta-Guinardó"
? "#238b45"
: nom == "Nou Barris"
? "#006d2c"
: nom == "Sant Andreu"
? "#00441b"
: nom == "Sant Martí"
? "#e0f3db"
: districte == "01"
? "#f7fcfd"
: districte == "02"
? "#e5f5f9"
: districte == "03"
? "#ccece6"
: districte == "04"
? "#99d8c9"
: districte == "05"
? "#66c2a4"
: districte == "06"
? "#41ae76"
: districte == "07"
? "#238b45"
: districte == "08"
? "#006d2c"
: districte == "09"
? "#00441b"
: districte == "10"
? "#e0f3db"
: "grey";
},
style: function (feature) {
//creem una funció per donar estil segons el NOM que tingui el districte
return {
fillColor: this.getColor(
feature.properties.nom,
feature.properties.districte
),
weight: 1.5,
opacity: 1,
color: "black",
dashArray: "3",
fillOpacity: 0.7,
};
},
highlightFeature: function (event) {
var layer = event.target; // Creem una varible layer on guardem el troçet de layer pel que passem per sobre
layer.setStyle({
weight: 5,
color: "#000000",
dashArray: "",
fillOpacity: 0.7,
});
if (!L.Browser.ie && !L.Browser.opera && !L.Browser.edge) {
layer.bringToFront();
}
},
resetHighlight: function (event) {
this.layers.resetStyle(event.target);
},
zoomToFeature: function (event) {
if (typeof onClick === "function") {
this.onClick(event);
}
this.map.fitBounds(event.target.getBounds());
},
onEachFeature: function (feature, layer) {
layer.on({
mouseover: this.highlightFeature,
mouseout: this.resetHighlight,
click: this.zoomToFeature,
});
},
},
});

74
client/public/js/models/geometries.js

@ -0,0 +1,74 @@
function GeometriesModel() {
this.layers = {
districtes: new GeometriesLayer("districtes"),
barris: new GeometriesLayer("barris"),
parceles: new GeometriesLayer("parceles"),
};
this.currentLayer = this.layers.districtes;
}
GeometriesModel.prototype.load = function (layerName, bbox) {
const layer = this.layers[layerName];
if (layer === void 0) throw new Exception("Unkown layer");
if (layer.shouldUpdate(bbox)) {
return fetch(layer.url(bbox), {
type: "GET",
cors: "no-cors",
cache: "no-cache",
headers: {
Accept: "application/json",
},
redirect: "error",
}).then(function (res) {
layer.lastURL = res.url.split(document.location.host)[1];
return res.json().then(function (data) {
layer.data = data;
return data;
});
});
} else {
return new Promise(function (res, rej) {
res(layer.data);
});
}
};
function GeometriesLayer(resource) {
this.resource = resource;
this.api = "rest";
this.lastURL = null;
let _data;
Object.defineProperty(this, "data", {
set: function (data) {
if (typeof value === "string") {
_data = JSON.parse(data);
} else {
_data = data;
}
},
get: function () {
return _data;
},
});
}
GeometriesLayer.prototype.bboxArray = function (bbox) {
return [bbox.west, bbox.north, bbox.east, bbox.south];
};
GeometriesLayer.prototype.shouldUpdate = function (bbox) {
return this.lastURL !== null ? this.lastURL !== this.url(bbox) : true;
};
GeometriesLayer.prototype.url = function (bbox) {
return (
"/" +
[this.api, this.resource, ...this.bboxArray(bbox)]
.map(function (chunk) {
return encodeURIComponent(chunk);
})
.join("/")
);
};

2
client/public/vendor/vue.min.js vendored

@ -1 +1 @@
/home/pau/leaflet/cadastre/desregistradores/client/node_modules/vue/dist/vue.min.js
/home/orzo/gitea/desregistradores/client/node_modules/vue/dist/vue.min.js

BIN
client/public/data/parcela.geojson → server/desregistradores.sqlite

Binary file not shown.

45
server/main.py

@ -1,32 +1,43 @@
# BUILT-INS
import os
import asyncio
# VENDOR
import aiohttp
from aiohttp import web
import aiofiles
dir = os.path.dirname(__file__)
routes = web.RouteTableDef()
app = web.Application()
app.router.add_static("/",
path=os.path.join(dir, "public"),
name="public")
app.router.add_static('/static',
path=os.path.join(dir, 'static'),
name='static')
async def index(request):
if request.method == 'GET':
async with aiofiles.open(os.path.join(dir, 'static', 'index.html')) as conn:
return web.Response(text=await conn.read(), content_type='text/html')
return web.HTTPMethodNotAllowed
# @routes.get("/")
# async def index(request):
# async with aiofiles.open(os.path.join(dir, "public", "index.html")) as conn:
# return web.Response(text=await conn.read(), content_type="text/html")
async def api(request):
path = request.match_info["path"]
try:
async with aiohttp.ClientSession() as session:
async with session.get('http://127.0.0.1:8050/%s' % path) as res:
return web.Response(text=await res.text(), content_type="application/json")
except Exception as e:
raise web.HTTPBadRequest(e)
@routes.get("/data/{resource}")
async def data(request):
# Obrir una conexió amb base de dates
# Fer la consulta que vulgui definia pels possibles paramatres de la url
file = request.match_info["resource"]
async with aiofiles.open(os.path.join(dir, "public", "data", file)) as conn:
return web.Response(text= await conn.read(), content_type="application/json")
app.add_routes([
web.get("/", index),
web.get("/rest/{path:.+}", api)
])
app.add_routes(routes)
if __name__ == "__main__":
web.run_app(app)

31
server/public/index.html

@ -1,31 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css" integrity="sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A==" crossorigin="" />
<link rel="stylesheet" href="/index.css">
<script src="/vendor/vue.min.js"></script>
<title>Map</title>
</head>
<body>
<div id="app"></div>
</body>
<script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js" integrity="sha512-XQoYMqMTK8LvdxXYG3nZ448hOEQiglfqkJs1NOQV44cWnUrBc8PkAOcXy20w0vlaXaVUearIOBhiXZ5V3ynxwA==" crossorigin=""></script>
<script src="/index.js"></script>
</html>

91
server/src/api/main.py

@ -1,13 +1,100 @@
# BUILT-INS
import json
# VENDOR
from fastapi import FastAPI, UploadFile, File
import aiofiles
import spatialite
app = FastAPI()
@app.get("/")
async def index():
return {"message": "Beinvguda al portal de les desregistradores"}
@app.get("/districte/{id}")
async def get_districte(id: int):
pass
@app.get("/districtes/{west}/{north}/{east}/{south}")
async def get_districtes(west: float, north: float, east: float, south: float):
db = spatialite.connect('desregistradores.sqlite')
cur = db.execute('''SELECT id, districte, nom, AsGeoJSON(wkb_geometry)
FROM districtes
WHERE Intersects(wkb_geometry, GeomFromText('POLYGON ((
{east} {north},
{east} {south},
{west} {south},
{west} {north},
{east} {north}
))', 4326));'''.format(west=west, north=north, east=east, south=south))
features = [{
"type": "Feature",
"geometry": json.loads(row[3]),
"properties": {"id": row[0], "districte": row[1], "nom": row[2]}
} for row in cur.fetchall()]
return {"type": "FeatureCollection", "features": features}
@app.get("/barri/{id}")
async def get_barri(id: int):
pass
@app.get("/barris/{west}/{north}/{east}/{south}")
async def get_barris(west: float, north: float, east: float, south: float):
db = spatialite.connect('desregistradores.sqlite')
cur = db.execute('''SELECT id, districte, nom, AsGeoJSON(wkb_geometry)
FROM barris
WHERE Intersects(wkb_geometry, GeomFromText('POLYGON ((
{east} {north},
{east} {south},
{west} {south},
{west} {north},
{east} {north}
))', 4326));'''.format(west=west, north=north, east=east, south=south))
features = [{
"type": "Feature",
"geometry": json.loads(row[3]),
"properties": {"id": row[0], "districte": row[1], "nom": row[2]}
} for row in cur.fetchall()]
return {"type": "FeatureCollection", "features": features}
@app.get("/parcele/{id}")
async def get_parcele(id: int):
pass
@app.get('/parceles/{west}/{north}/{east}/{south}')
async def get_parceles(west: float, north: float, east: float, south: float):
db = spatialite.connect('desregistradores.sqlite')
cur = db.execute('''SELECT id, refcat, AsGeoJSON(wkb_geometry)
FROM parceles
WHERE Intersects(wkb_geometry, GeomFromText('POLYGON((
{east} {north},
{east} {south},
{west} {south},
{west} {north},
{east} {north}
))', 4326));'''.format(west=west, north=north, east=east, south=south))
features = [{
"type": "Feature",
"geometry": json.loads(row[2]),
"properties": {"id": row[0], "refcat": row[1]}
} for row in cur.fetchall()]
return {"type": "FeatureCollection", "features": features}
@app.post("/upload")
async def upload(file: UploadFile = File(...)):
return {"filename": file.filename}
return {"filename": file.filename}

28
server/static/index.html

@ -0,0 +1,28 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link
rel="stylesheet"
href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css"
integrity="sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A=="
crossorigin=""
/>
<link rel="stylesheet" href="/static/index.css" />
<script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"></script>
<script src="/static/vendor/vue.min.js"></script>
<title>Map</title>
</head>
<body>
<div id="app"></div>
<script src="/static/js/models/geometries.js"></script>
<script src="/static/js/map.js"></script>
<script src="/static/js/index.js"></script>
</body>
</html>
Loading…
Cancel
Save