/**
* Representa una entidad vehiculo en el Mapa
*/
class Vehiculo {
/**
*
* @param {number} lat Latitud de la posición geográfica
* @param {number} lng Longitud de la posición geográfica
* @param {string} tipoDeVehiculo Por defecto 'SAMU'
* @param {string} horario Horario del día que está disponible el vehiculo
* @param {number} tiempoDeIsocrona Alcance de la isocrona del vehiculo
* @param {object} elMapa Referencia al mapa de Leaflet
*/
constructor(lat = 0, lng = 0, tipoDeVehiculo = 'SVA', disponibilidad = '12 AM', tiempoDeIsocrona = 10, elMapa = null, descripcion = '') {
this.posicion = {};
this.posicion.lat = parseFloat(lat);
this.posicion.lng = parseFloat(lng);
this.disponibilidad = disponibilidad;
this.descripcion = descripcion;
this.tipoDeVehiculo = tipoDeVehiculo;
this.tiempoDeIsocrona = tiempoDeIsocrona;
this.elMapa = elMapa;
this.isocrona = null;
this.isocronaSimple = null;
this.enlaceABackend = new EnlaceABackend();
this.poblacionCubierta = null;
this.isBusy = false;
// ------------------------------------------
/*
Setup del marcador
La idea es que dependiendo de si es un SVB o un SAMU
el marcador sea rojo o azul respectivamente
También cambia el color de la isocrona
*/
// ------------------------------------------
let colorMarcador;
switch (tipoDeVehiculo) {
case 'SVB':
colorMarcador = 'blue';
this.colorIsocrona = '#129fe6'; // Color azul en hex
break;
case 'SVA':
colorMarcador = 'red';
this.colorIsocrona = '#e61212' // Color rojo en hex
break;
default:
colorMarcador = 'red';
this.colorIsocrona = '#e61212'
break;
}
let icono = L.AwesomeMarkers.icon({
icon: 'ambulance',
markerColor: colorMarcador,
prefix: 'fa'
});
this.marcador = L.marker(this.posicion, {
icon: icono,
draggable: true
}).addTo(this.elMapa);
this.marcador.on('click', (e) => {
this.setVisibilidadIsocrona(!this.marcador.isPopupOpen());
// Uso la negación para que realice el update
// al ABRIR el popup, con lo cual la función
// devuelve que sigue cerrado
if (!this.marcador.isPopupOpen() && !this.poblacionCubierta) {
this.updatePoblacionCubierta();
}
if (selectionMode) {
this.setVisibilidadIsocrona(false);
anyadirVehiculo(this);
setSelectionMode(false);
}
})
this.marcador.bindPopup(`
<b>${descripcion}<b>
<p>Disponibilidad: ${disponibilidad}</p>
<p>Población cubierta: Cargando...</p>
`);
this.updateContenidoPopup(descripcion, disponibilidad, 'Cargando...');
}
/**
* Desplaza el vehiculo a la posición indicada
* por las coordenadas proporcionadas
* @param {number} lat
* @param {number} lng
*/
desplazarA(lat, lng) {
this.posicion.lat = lat;
this.posicion.lng = lng;
let nuevaPosicionMarcador = new L.LatLng(lat,lng);
this.marcador.setLatLng(nuevaPosicionMarcador).update();
}
/**
* Actualiza el alcance de la isocrona
* @param {number} nuevoTiempo Nuevo tiempo para la isocrona
* @param {function} onAcabado Callback ejecutado al acabar la operación
*/
actualizarIsocrona(nuevoTiempo = 10, onAcabado = (success, failure) => {}) {
this.setVisibilidadIsocrona(false);
if (elMapa.hasLayer(this.isocrona)) elMapa.removeLayer(this.isocrona);
this.tiempoDeIsocrona = nuevoTiempo;
this.updateContenidoPopup(
this.descripcion,
this.disponibilidad,
'Cargando...'
)
this.enlaceABackend.getIsocrona(
this.posicion.lat,
this.posicion.lng,
this.tiempoDeIsocrona,
(res, error) => {
if (error) {
this.isocrona = null;
this.isocronaSimple = null;
this.poblacionCubierta = null;
this.setVisibilidadIsocrona(false);
// Success - Failure
onAcabado(null, error);
return;
}
if (res.error) {
this.isocrona = null;
this.isocronaSimple = null;
this.poblacionCubierta = null;
onAcabado(null, res.error);
return;
}
this.isocronaSimple = turf.simplify(res, {
tolerance: this.calcularToleranceDePoligono(),
highQuality: true
});
/*L.geoJSON(this.isocronaSimple, {
style: {
color: '#129fe6'
}
}).addTo(this.elMapa); */
this.isocrona = L.geoJSON(res, {
style: {
color: this.colorIsocrona
}
});
this.updatePoblacionCubierta();
onAcabado('Success', null);
}
)
}
/**
* Calcula la tolerancia para la simplifciación
* del polígono en función del tiempo de isócrona
* actual del vehículo
*
* @returns toleranciaSimplificacion
*/
calcularToleranceDePoligono() {
// En esta función haremos un map
// para calcular el parámetro "tolerance" de
// simplificación del poligono
// Tiempo min = 1 min
// Tiempo max = 60 min
// Tolerancia min = 0.001
// Tolerancia max = 0.05
const lowerTiempo = 1;
const upperTiempo = 30;
const lowerTolerance = 0.0002;
const upperTolerance = 0.01;
// Algoritmo: [A, B] --> [a, b]
// (valor - A) * (b - a)/(B - A) + a
let segundoFactor = (upperTolerance - lowerTolerance)/(upperTiempo - lowerTiempo) + lowerTolerance;
let tolerance = (this.tiempoDeIsocrona - lowerTiempo) * segundoFactor;
return tolerance;
}
/**
* Establece la visibilidad de la isocrona en el Mapa
* @param {boolean} visibilidad
*/
setVisibilidadIsocrona(visibilidad = false) {
if (!this.isocrona) return;
if (!visibilidad) {
if (this.isocrona) {
this.elMapa.removeLayer(this.isocrona);
}
return;
}
if (this.isocrona) {
this.isocrona.addTo(this.elMapa);
}
}
/**
*
* @returns {boolean} Visibilidad de la isocrona en el mapa
*/
esLaIsocronaVisible() {
return this.elMapa.hasLayer(this.isocrona);
}
/**
* Comprueba si existe solape entre isocronas y devuelve
* un objeto Overlap / null
* @param {Vehiculo} otroVehiculo
* @returns interseccion
*/
checkSolapeCon(otroVehiculo) {
if (!otroVehiculo.isocrona || !this.isocrona) return;
// Esta es la mejor manera que encontré de
// revertir al objeto original isocrona
// de antes del procesado que hace leaflet
let property1 = Object.keys(this.isocrona._layers)[0];
let candidate1 = this.isocrona._layers[property1].feature;
let property2 = Object.keys(otroVehiculo.isocrona._layers)[0];
let candidate2 = otroVehiculo.isocrona._layers[property2].feature;
let interseccion = turf.intersect(candidate1, candidate2);
return interseccion;
}
/**
* Acción a realizar cuando se arrastra el marcador
* @param {Position} newPos {Lat, lng}
* @param {function} callback Callback(isocrona//null)
* @returns
*/
onDragMarcador(newPos, callback) {
if (!this.marcador.isPopupOpen() || this.isocrona) {
callback(this.isocrona);
return;
}
this.setVisibilidadIsocrona(false);
this.updateContenidoPopup(
this.descripcion,
this.disponibilidad,
'Cargando...'
)
this.actualizarIsocrona(this.tiempoDeIsocrona, (worked, error) => {
if (worked && !error) {
this.setVisibilidadIsocrona(true);
callback(this.isocrona);
} else {
console.error(error + ' ' + worked)
callback(null);
}
});
}
/**
* Informa sobre si el vehiculo tiene localmente
* la isocrona de la superficie que puede cubrir
* @returns {boolean}
*/
tieneIsocrona() {
return this.isocrona != null;
}
/**
* Actualiza el número de la población contenida en la isocrona
* del vehiculo
* @returns {void}
*/
updatePoblacionCubierta() {
if (!this.isocrona) return;
//const propiedadFeature = Object.keys(this.isocrona._layers)[0];
//const feature = this.isocrona._layers[propiedadFeature].feature;
this.enlaceABackend.getEstimacionPoblacion_WorldPop(this.isocronaSimple, (res, err) => {
if (err || typeof res.total_population !== 'number') {
this.updateContenidoPopup(
this.descripcion,
this.disponibilidad,
'No disponible'
);
return;
}
// La api suele devolver un número con decimales
// lo rendondeamos a un número entero
try {
res = Math.floor(res.total_population);
this.poblacionCubierta = res;
this.updateContenidoPopup(
this.descripcion,
this.disponibilidad,
this.poblacionCubierta
);
} catch (error) {
console.error(error);
this.updateContenidoPopup(
this.descripcion,
this.disponibilidad,
'No disponible'
);
return;
}
})
}
/**
* Actualiza el contenido del popup del marcador
* @param {string} descripcion Descripción del vehiculo
* @param {string} disponibilidad Disponibilidad del vehiculo
* @param {string} textoPoblacion Texto en el apartado de población cubierta
*/
updateContenidoPopup(descripcion, disponibilidad, textoPoblacion) {
let newContent = `
<table class="table">
<tbody>
<tr>
<th scope="row">Descripción:</th>
<td>${descripcion}</td>
</tr>
<tr>
<th scope="row">Disponibilidad:</th>
<td>${disponibilidad}</td>
</tr>
<tr>
<th scope="row">Población cubierta:</th>
<td>${textoPoblacion}</td>
</tr>
</tbody>
</table>
`
this.marcador.setPopupContent(newContent);
}
/**
* Elimina cualquier aspecto del objeto del mapa
*/
destruir() {
this.setVisibilidadIsocrona(false);
this.elMapa.removeLayer(this.marcador);
}
}