2022-11-22 22:03:16 -03:00
|
|
|
import * as L from 'leaflet-contextmenu';
|
2023-02-16 22:54:29 -03:00
|
|
|
import { Feature, FeatureCollection, Point } from 'geojson';
|
2022-07-26 17:15:24 -04:00
|
|
|
|
|
|
|
|
interface PlaceModel {
|
2022-11-22 22:03:16 -03:00
|
|
|
id: number | null;
|
2022-07-26 17:15:24 -04:00
|
|
|
name: string;
|
|
|
|
|
address: string;
|
|
|
|
|
open_hours: string;
|
|
|
|
|
icon: string;
|
|
|
|
|
description: string;
|
|
|
|
|
latitude: number;
|
|
|
|
|
longitude: number;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function loadPlaces(): Promise<Array<PlaceModel>> {
|
2022-11-22 22:03:16 -03:00
|
|
|
return await fetch('places').then(response => response.json());
|
2022-07-26 17:15:24 -04:00
|
|
|
}
|
|
|
|
|
|
2022-11-22 22:03:16 -03:00
|
|
|
function toFeature(place: PlaceModel): Feature {
|
|
|
|
|
return {
|
|
|
|
|
"type": "Feature",
|
|
|
|
|
"properties": {
|
|
|
|
|
"name": place.name,
|
|
|
|
|
"address": place.address,
|
|
|
|
|
"open_hours": place.open_hours,
|
|
|
|
|
"icon": place.icon,
|
|
|
|
|
"description": place.description
|
|
|
|
|
},
|
|
|
|
|
"geometry": {
|
|
|
|
|
"type": "Point",
|
|
|
|
|
"coordinates": [place.longitude, place.latitude]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function toLeafletPlaces(backendPlaces: Array<PlaceModel>): L.GeoJSON {
|
2022-08-01 22:21:27 -04:00
|
|
|
let result: FeatureCollection = {
|
|
|
|
|
type: "FeatureCollection",
|
|
|
|
|
features: new Array<Feature>(),
|
|
|
|
|
}
|
2022-07-26 17:15:24 -04:00
|
|
|
for (const place of backendPlaces) {
|
2022-11-22 22:03:16 -03:00
|
|
|
result.features.push(toFeature(place));
|
2022-07-26 17:15:24 -04:00
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-22 22:03:16 -03:00
|
|
|
let placesLayer: L.GeoJSON;
|
|
|
|
|
let places = new Map<string, PlaceModel>();
|
|
|
|
|
|
|
|
|
|
function toStr(latlng: L.LatLngLiteral) {
|
|
|
|
|
return latlng.lng + "|" + latlng.lat;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getPlaceFromForm(): PlaceModel {
|
|
|
|
|
const idStr = (document.getElementById("id") as HTMLInputElement).value;
|
|
|
|
|
const id = idStr == "" ? null : parseInt(idStr);
|
|
|
|
|
const name =
|
|
|
|
|
(document.getElementById("name") as HTMLInputElement).value;
|
|
|
|
|
const address =
|
|
|
|
|
(document.getElementById("address") as HTMLInputElement).value;
|
|
|
|
|
const open_hours =
|
|
|
|
|
(document.getElementById("open_hours") as HTMLTextAreaElement).value;
|
|
|
|
|
const icon =
|
|
|
|
|
(document.getElementById("icon") as HTMLSelectElement).value;
|
|
|
|
|
const description =
|
|
|
|
|
(document.getElementById("description") as HTMLTextAreaElement).value;
|
|
|
|
|
const latitude = parseFloat(
|
|
|
|
|
(document.getElementById("lat") as HTMLInputElement).value);
|
|
|
|
|
const longitude = parseFloat(
|
|
|
|
|
(document.getElementById("long") as HTMLSelectElement).value);
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
id: id,
|
|
|
|
|
name: name,
|
|
|
|
|
address: address,
|
|
|
|
|
open_hours: open_hours,
|
|
|
|
|
icon: icon,
|
|
|
|
|
description: description,
|
|
|
|
|
latitude: latitude,
|
|
|
|
|
longitude: longitude,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function clearForm(): void {
|
|
|
|
|
/* Get the form elements*/
|
|
|
|
|
const idInput = (document.getElementById("id") as HTMLInputElement);
|
|
|
|
|
const latInput = (document.getElementById("lat") as HTMLInputElement);
|
|
|
|
|
const longInput = (document.getElementById("long") as HTMLInputElement);
|
|
|
|
|
const nameInput = (document.getElementById("name") as HTMLInputElement);
|
|
|
|
|
const addressInput = (document.getElementById("address") as HTMLInputElement);
|
|
|
|
|
const openHoursArea = (document.getElementById("open_hours") as HTMLTextAreaElement);
|
|
|
|
|
const descriptionArea = (document.getElementById("description") as HTMLTextAreaElement);
|
|
|
|
|
|
|
|
|
|
/* Clear them */
|
|
|
|
|
idInput.value = "";
|
|
|
|
|
latInput.value = "";
|
|
|
|
|
longInput.value = "";
|
|
|
|
|
nameInput.value = "";
|
|
|
|
|
addressInput.value = "";
|
|
|
|
|
openHoursArea.value = "";
|
|
|
|
|
descriptionArea.value = "";
|
|
|
|
|
|
|
|
|
|
/* Clear the callback */
|
|
|
|
|
document.getElementById("button").onclick = null;
|
|
|
|
|
|
|
|
|
|
/* Now you see it, now you don't*/
|
|
|
|
|
document.getElementById("modal").style.display = "none";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function createPlace(): Promise<void> {
|
|
|
|
|
const newPlace = getPlaceFromForm();
|
|
|
|
|
|
|
|
|
|
await fetch('places', {
|
|
|
|
|
method: 'PUT',
|
|
|
|
|
headers: {
|
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
|
},
|
|
|
|
|
body: JSON.stringify(newPlace),
|
|
|
|
|
})
|
|
|
|
|
.then((response) => response.json())
|
|
|
|
|
.then((place: PlaceModel) => {
|
|
|
|
|
places.set(
|
|
|
|
|
toStr({ lat: place.latitude, lng: place.longitude }),
|
|
|
|
|
place
|
|
|
|
|
);
|
|
|
|
|
placesLayer.addData(toFeature(place));
|
|
|
|
|
clearForm();
|
|
|
|
|
alert("Lugar añadido exitosamente");
|
|
|
|
|
})
|
|
|
|
|
.catch((error) => alert(error));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function editPlace(): Promise<void> {
|
|
|
|
|
const newPlace = getPlaceFromForm();
|
|
|
|
|
|
|
|
|
|
await fetch('places', {
|
|
|
|
|
method: 'PUT',
|
|
|
|
|
headers: {
|
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
|
},
|
|
|
|
|
body: JSON.stringify(newPlace),
|
|
|
|
|
})
|
|
|
|
|
.then((response) => response.json())
|
|
|
|
|
.then((place: PlaceModel) => {
|
|
|
|
|
places.set(
|
|
|
|
|
toStr({ lat: place.latitude, lng: place.longitude }),
|
|
|
|
|
place
|
|
|
|
|
);
|
|
|
|
|
clearForm();
|
|
|
|
|
alert("Lugar guardado exitosamente. Para ver los cambios, recargue la página.");
|
|
|
|
|
})
|
|
|
|
|
.catch((error) => alert(error));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const enum Operation {
|
|
|
|
|
Create = "create",
|
|
|
|
|
Edit = "edit",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface MapEvent {
|
|
|
|
|
latlng: L.LatLng;
|
|
|
|
|
relatedTarget: L.Marker | L.Path | undefined;
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-10 01:12:59 -03:00
|
|
|
async function getAddressReverse(lat: number, long: number): Promise<string> {
|
|
|
|
|
const nominatimEndpoint = `https://nominatim.openstreetmap.org/reverse?lat=${lat}&lon=${long}&format=json`
|
|
|
|
|
const address = (await fetch(nominatimEndpoint).then(response => response.json())).address;
|
|
|
|
|
return `${address.road} ${address.house_number}, ${address.city}`;
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-01 22:21:27 -04:00
|
|
|
async function setupMap(): Promise<void> {
|
2022-11-22 22:03:16 -03:00
|
|
|
/* Create/Edit form */
|
|
|
|
|
const modal = document.getElementById("modal");
|
|
|
|
|
const closeButton = document.getElementById("close");
|
|
|
|
|
|
|
|
|
|
closeButton.onclick = function() {
|
|
|
|
|
modal.style.display = "none";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
window.onclick = function(e: Event) {
|
|
|
|
|
if (e.target == modal) {
|
|
|
|
|
modal.style.display = "none";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-10 01:12:59 -03:00
|
|
|
async function openForm(op: Operation, lat: number, long: number): Promise<void> {
|
2022-11-22 22:03:16 -03:00
|
|
|
/* Fill the form for us */
|
|
|
|
|
const h1 = modal.getElementsByTagName("h1")[0];
|
|
|
|
|
if (op == Operation.Create) {
|
|
|
|
|
clearForm()
|
|
|
|
|
h1.innerText = "Añadir lugar nuevo";
|
|
|
|
|
} else {
|
|
|
|
|
h1.innerText = "Editar lugar";
|
|
|
|
|
}
|
|
|
|
|
const latInput = (document.getElementById("lat") as HTMLInputElement);
|
|
|
|
|
const longInput = (document.getElementById("long") as HTMLInputElement);
|
|
|
|
|
latInput.value = lat.toString();
|
|
|
|
|
longInput.value = long.toString();
|
|
|
|
|
|
2023-03-10 01:12:59 -03:00
|
|
|
const addressInput = (document.getElementById("address") as HTMLInputElement);
|
2022-11-22 22:03:16 -03:00
|
|
|
if (op == Operation.Edit) {
|
|
|
|
|
const place = places.get(toStr({ lat: lat, lng: long }));
|
|
|
|
|
console.log(toStr({ lat: lat, lng: long }));
|
|
|
|
|
/*Get the form elements*/
|
|
|
|
|
const idInput = (document.getElementById("id") as HTMLInputElement);
|
|
|
|
|
const nameInput = (document.getElementById("name") as HTMLInputElement);
|
|
|
|
|
const openHoursArea = (document.getElementById("open_hours") as HTMLTextAreaElement);
|
|
|
|
|
const iconSelect = (document.getElementById("icon") as HTMLSelectElement);
|
|
|
|
|
const descriptionArea = (document.getElementById("description") as HTMLTextAreaElement);
|
|
|
|
|
|
|
|
|
|
/* And set them */
|
|
|
|
|
idInput.value = place.id.toString();
|
|
|
|
|
nameInput.value = place.name;
|
|
|
|
|
addressInput.value = place.address;
|
|
|
|
|
openHoursArea.value = place.open_hours;
|
|
|
|
|
iconSelect.value = place.icon;
|
|
|
|
|
descriptionArea.value = place.description;
|
2023-03-10 01:12:59 -03:00
|
|
|
} else {
|
|
|
|
|
getAddressReverse(lat, long).then(
|
|
|
|
|
(address) => {
|
|
|
|
|
if (addressInput.value.length == 0) {
|
|
|
|
|
addressInput.value = address;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
);
|
2022-11-22 22:03:16 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Plug callbacks */
|
|
|
|
|
if (op == Operation.Create) {
|
|
|
|
|
document.getElementById("button").onclick = createPlace;
|
|
|
|
|
} else {
|
|
|
|
|
document.getElementById("button").onclick = editPlace;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Make it appear */
|
|
|
|
|
modal.style.display = "block";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function openCreateForm(e: MapEvent) {
|
|
|
|
|
const lat = e.latlng.lat;
|
|
|
|
|
const long = e.latlng.lng;
|
|
|
|
|
openForm(Operation.Create, lat, long);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function openEditForm(e: MapEvent) {
|
|
|
|
|
const marker = (e.relatedTarget as L.Marker);
|
|
|
|
|
const lat = marker.getLatLng().lat;
|
|
|
|
|
const long = marker.getLatLng().lng;
|
|
|
|
|
openForm(Operation.Edit, lat, long);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Get places from backend */
|
2022-07-26 17:15:24 -04:00
|
|
|
const backendPlaces = await loadPlaces();
|
|
|
|
|
const leafletPlaces = toLeafletPlaces(backendPlaces);
|
2022-11-22 22:03:16 -03:00
|
|
|
for (const place of backendPlaces) {
|
|
|
|
|
places.set(
|
|
|
|
|
toStr({ lat: place.latitude, lng: place.longitude }),
|
|
|
|
|
place
|
|
|
|
|
);
|
|
|
|
|
}
|
2022-07-26 17:15:24 -04:00
|
|
|
|
|
|
|
|
/* Set up the map*/
|
2022-11-22 22:03:16 -03:00
|
|
|
const map = new L.map('map', {
|
|
|
|
|
contextmenu: true,
|
|
|
|
|
contextmenuWidth: 140,
|
|
|
|
|
contextmenuItems: [
|
|
|
|
|
{
|
|
|
|
|
text: 'Añadir lugar',
|
|
|
|
|
callback: openCreateForm
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
});
|
2022-07-26 17:15:24 -04:00
|
|
|
|
|
|
|
|
/* Create the tile layer with correct attribution*/
|
|
|
|
|
const osmUrl = 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
|
|
|
|
|
const osmAttrib = 'Mapa © <a href="https://openstreetmap.org">OpenStreetMap</a>';
|
|
|
|
|
const osm = new L.TileLayer(osmUrl, { minZoom: 4, maxZoom: 20, attribution: osmAttrib });
|
|
|
|
|
|
|
|
|
|
/* Start the map in Santiago */
|
|
|
|
|
map.setView(new L.LatLng(-33.45, -70.666667), 13);
|
|
|
|
|
/* Try to get user position, if not, put the map in Santiago again */
|
|
|
|
|
map.locate({ setView: true, maxZoom: 16 })
|
|
|
|
|
.on('locationerror', function(_event: L.LocationEvent) {
|
|
|
|
|
map.setView(new L.LatLng(-33.45, -70.666667), 13);
|
|
|
|
|
});
|
|
|
|
|
map.addLayer(osm);
|
|
|
|
|
|
|
|
|
|
function onEachFeature(feature: Feature, layer: L.Layer) {
|
|
|
|
|
if (feature.properties) {
|
|
|
|
|
let popupStr = "<h3>" + feature.properties.name + "</h3>";
|
|
|
|
|
popupStr += "<ul>"
|
|
|
|
|
if (feature.properties.address)
|
|
|
|
|
popupStr += "<li><b>Dirección:</b> " + feature.properties.address + "</li>";
|
|
|
|
|
if (feature.properties.open_hours)
|
|
|
|
|
popupStr += "<li><b>Horario:</b> " + feature.properties.open_hours + "</li>";
|
|
|
|
|
if (feature.properties.description)
|
|
|
|
|
popupStr += "<li>" + feature.properties.description + "</li>";
|
2023-02-16 22:54:29 -03:00
|
|
|
|
|
|
|
|
const lnglat = (feature.geometry as Point).coordinates;
|
|
|
|
|
popupStr += "<a href=\"https://www.google.com/maps/dir//" +
|
|
|
|
|
lnglat[1] + "," + lnglat[0] + "/@" + lnglat[1] + "," + lnglat[0] +
|
|
|
|
|
",15z\" target=\"_blank\">GMaps</a>"
|
2022-07-26 17:15:24 -04:00
|
|
|
popupStr += "</ul>";
|
|
|
|
|
|
|
|
|
|
layer.bindPopup(popupStr);
|
2022-11-22 22:03:16 -03:00
|
|
|
layer.bindContextMenu({
|
|
|
|
|
contextmenu: true,
|
|
|
|
|
contextmenuInheritItems: false,
|
|
|
|
|
contextmenuItems: [{
|
|
|
|
|
text: 'Editar',
|
|
|
|
|
callback: openEditForm,
|
|
|
|
|
}]
|
|
|
|
|
});
|
2022-07-26 17:15:24 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Icons */
|
|
|
|
|
const icons = new Map<string, L.Icon>();
|
|
|
|
|
icons.set('bar', new L.Icon({ iconUrl: 'icons/bar.svg' }));
|
2022-08-05 22:32:28 -04:00
|
|
|
icons.set('coffee', new L.Icon({ iconUrl: 'icons/coffee.svg' }));
|
2022-08-08 17:43:08 -04:00
|
|
|
icons.set('cinema', new L.Icon({ iconUrl: 'icons/film.svg' }));
|
2022-07-26 17:15:24 -04:00
|
|
|
icons.set('dining', new L.Icon({ iconUrl: 'icons/dining.svg' }));
|
|
|
|
|
icons.set('food', new L.Icon({ iconUrl: 'icons/food.svg' }));
|
|
|
|
|
icons.set('jazz', new L.Icon({ iconUrl: 'icons/saxophone.svg' }));
|
|
|
|
|
icons.set('library', new L.Icon({ iconUrl: 'icons/book.svg' }));
|
|
|
|
|
icons.set('marker', new L.Icon({ iconUrl: 'icons/marker.svg' }));
|
2022-08-05 22:32:28 -04:00
|
|
|
icons.set('mask', new L.Icon({ iconUrl: 'icons/mask.svg' }));
|
2022-07-26 17:15:24 -04:00
|
|
|
icons.set('museum', new L.Icon({ iconUrl: 'icons/museum.svg' }));
|
|
|
|
|
icons.set('shop', new L.Icon({ iconUrl: 'icons/store.svg' }));
|
|
|
|
|
|
2022-08-05 22:32:28 -04:00
|
|
|
for (let [_name, icon] of icons) {
|
2022-07-26 17:15:24 -04:00
|
|
|
icon.options.iconSize = [36, 36];
|
|
|
|
|
icon.options.popupAnchor = [0, -18];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function pointToLayer(feature: Feature, latlng: L.LatLng) {
|
|
|
|
|
let markerIcon = null;
|
|
|
|
|
if (feature.properties && feature.properties.icon) {
|
|
|
|
|
markerIcon = icons.get(feature.properties.icon);
|
|
|
|
|
}
|
|
|
|
|
if (markerIcon !== null && markerIcon !== undefined)
|
|
|
|
|
return L.marker(latlng, { icon: markerIcon });
|
|
|
|
|
else
|
|
|
|
|
return L.marker(latlng);
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-22 22:03:16 -03:00
|
|
|
placesLayer = L.geoJSON(leafletPlaces, {
|
2022-07-26 17:15:24 -04:00
|
|
|
onEachFeature: onEachFeature,
|
|
|
|
|
pointToLayer: pointToLayer
|
2022-11-22 22:03:16 -03:00
|
|
|
})
|
|
|
|
|
map.addLayer(placesLayer);
|
2022-07-26 17:15:24 -04:00
|
|
|
}
|