Usar herramientas de medición en un mapa creado con React-Leaflet es extremadamente útil por varias razones. Estas herramientas permiten a los usuarios interactuar con el mapa de manera más dinámica y obtener información precisa sobre las distancias y áreas geográficas directamente en la interfaz del usuario. Aquí te explico para qué son útiles y cómo pueden mejorar la experiencia del usuario.
Utilidades de las Herramientas de Medición
Medición de Distancias:
- Propósito: Permite a los usuarios medir la distancia entre dos o más puntos en el mapa.
- Aplicaciones: Esto es útil en aplicaciones como planificación de rutas, cálculo de distancias de viaje, y en contextos educativos donde los usuarios pueden querer aprender más sobre la geografía de un área.
Medición de Áreas:
- Propósito: Permite a los usuarios medir el área de una región definida en el mapa.
- Aplicaciones: Útil para la gestión de terrenos, la agricultura, la planificación urbana y la evaluación de propiedades. Los usuarios pueden calcular el tamaño de una parcela de tierra o una región específica en un proyecto de desarrollo.
Interacción Mejorada:
- Proporciona a los usuarios una forma interactiva de explorar datos espaciales, haciendo el mapa más informativo y atractivo.
- Permite a los usuarios realizar tareas personalizadas que se adapten a sus necesidades específicas, como medir el área de un parque para un evento o determinar la distancia entre dos puntos de interés.
Apoyo a Decisiones:
- Facilita la toma de decisiones basada en datos precisos directamente desde el mapa, como determinar la viabilidad de proyectos de construcción o analizar el impacto ambiental de un desarrollo planificado.
Educación y Aprendizaje:
- En entornos educativos, permite a estudiantes y profesores realizar mediciones y análisis geográficos que pueden ser vitales para cursos de geografía, ciencias ambientales y estudios urbanos.
Primero, asegúrate de tener instalados React-Leaflet y leaflet-ruler en tu proyecto:
npm install react-leaflet leaflet leaflet-ruler
Codigo de App.js
import { MapContainer, TileLayer } from "react-leaflet";
import "leaflet/dist/leaflet.css";
import "./styles.css";
import LeafletRuler from "./LeafletRuler";
export default function App() {
const position = [51.505, -0.09];
return (
<MapContainer center={position} zoom={13} style={{ height: "100vh" }}>
<TileLayer
attribution='© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
<LeafletRuler />
</MapContainer>
);
}
Codigo LeafletRuler.js
import { useEffect } from "react";
import { useMap } from "react-leaflet";
import L from "leaflet";
import "./leaflet-ruler.css";
import "./leaflet-ruler";
export default function LeafletRuler() {
const map = useMap();
useEffect(() => {
if (!map) return;
L.control.ruler().addTo(map);
}, [map]);
return null;
}
Codigo de lealfet-ruler.js
import L from "leaflet";
(function (factory, window) {
/* eslint-disable */
if (typeof define === "function" && define.amd) {
define(["leaflet"], factory);
} else if (typeof exports === "object") {
module.exports = factory(require("leaflet"));
}
if (typeof window !== "undefined" && window.L) {
window.L.Ruler = factory(L);
}
})(function (L) {
L.Control.Ruler = L.Control.extend({
options: {
position: "topright",
circleMarker: {
color: "red",
radius: 2
},
lineStyle: {
color: "red",
dashArray: "1,6"
},
lengthUnit: {
display: "km",
decimal: 2,
factor: null,
label: "Distance:"
},
angleUnit: {
display: "°",
decimal: 2,
factor: null,
label: "Bearing:"
}
},
onAdd: function (map) {
this._map = map;
this._container = L.DomUtil.create("div", "leaflet-bar");
this._container.classList.add("leaflet-ruler");
L.DomEvent.disableClickPropagation(this._container);
L.DomEvent.on(this._container, "click", this._toggleMeasure, this);
this._choice = false;
this._defaultCursor = this._map._container.style.cursor;
this._allLayers = L.layerGroup();
return this._container;
},
onRemove: function () {
L.DomEvent.off(this._container, "click", this._toggleMeasure, this);
},
_toggleMeasure: function () {
this._choice = !this._choice;
this._clickedLatLong = null;
this._clickedPoints = [];
this._totalLength = 0;
if (this._choice) {
this._map.doubleClickZoom.disable();
L.DomEvent.on(this._map._container, "keydown", this._escape, this);
L.DomEvent.on(this._map._container, "dblclick", this._closePath, this);
this._container.classList.add("leaflet-ruler-clicked");
this._clickCount = 0;
this._tempLine = L.featureGroup().addTo(this._allLayers);
this._tempPoint = L.featureGroup().addTo(this._allLayers);
this._pointLayer = L.featureGroup().addTo(this._allLayers);
this._polylineLayer = L.featureGroup().addTo(this._allLayers);
this._allLayers.addTo(this._map);
this._map._container.style.cursor = "crosshair";
this._map.on("click", this._clicked, this);
this._map.on("mousemove", this._moving, this);
} else {
this._map.doubleClickZoom.enable();
L.DomEvent.off(this._map._container, "keydown", this._escape, this);
L.DomEvent.off(this._map._container, "dblclick", this._closePath, this);
this._container.classList.remove("leaflet-ruler-clicked");
this._map.removeLayer(this._allLayers);
this._allLayers = L.layerGroup();
this._map._container.style.cursor = this._defaultCursor;
this._map.off("click", this._clicked, this);
this._map.off("mousemove", this._moving, this);
}
},
_clicked: function (e) {
this._clickedLatLong = e.latlng;
this._clickedPoints.push(this._clickedLatLong);
L.circleMarker(this._clickedLatLong, this.options.circleMarker).addTo(
this._pointLayer
);
if (
this._clickCount > 0 &&
!e.latlng.equals(this._clickedPoints[this._clickedPoints.length - 2])
) {
if (this._movingLatLong) {
L.polyline(
[this._clickedPoints[this._clickCount - 1], this._movingLatLong],
this.options.lineStyle
).addTo(this._polylineLayer);
}
var text;
this._totalLength += this._result.Distance;
if (this._clickCount > 1) {
text =
"<b>" +
this.options.angleUnit.label +
"</b> " +
this._result.Bearing.toFixed(this.options.angleUnit.decimal) +
" " +
this.options.angleUnit.display +
"<br><b>" +
this.options.lengthUnit.label +
"</b> " +
this._totalLength.toFixed(this.options.lengthUnit.decimal) +
" " +
this.options.lengthUnit.display;
} else {
text =
"<b>" +
this.options.angleUnit.label +
"</b> " +
this._result.Bearing.toFixed(this.options.angleUnit.decimal) +
" " +
this.options.angleUnit.display +
"<br><b>" +
this.options.lengthUnit.label +
"</b> " +
this._result.Distance.toFixed(this.options.lengthUnit.decimal) +
" " +
this.options.lengthUnit.display;
}
L.circleMarker(this._clickedLatLong, this.options.circleMarker)
.bindTooltip(text, { permanent: true, className: "result-tooltip" })
.addTo(this._pointLayer)
.openTooltip();
}
this._clickCount++;
},
_moving: function (e) {
if (this._clickedLatLong) {
L.DomEvent.off(this._container, "click", this._toggleMeasure, this);
this._movingLatLong = e.latlng;
if (this._tempLine) {
this._map.removeLayer(this._tempLine);
this._map.removeLayer(this._tempPoint);
}
var text;
this._addedLength = 0;
this._tempLine = L.featureGroup();
this._tempPoint = L.featureGroup();
this._tempLine.addTo(this._map);
this._tempPoint.addTo(this._map);
this._calculateBearingAndDistance();
this._addedLength = this._result.Distance + this._totalLength;
L.polyline(
[this._clickedLatLong, this._movingLatLong],
this.options.lineStyle
).addTo(this._tempLine);
if (this._clickCount > 1) {
text =
"<b>" +
this.options.angleUnit.label +
"</b> " +
this._result.Bearing.toFixed(this.options.angleUnit.decimal) +
" " +
this.options.angleUnit.display +
"<br><b>" +
this.options.lengthUnit.label +
"</b> " +
this._addedLength.toFixed(this.options.lengthUnit.decimal) +
" " +
this.options.lengthUnit.display +
'<br><div class="plus-length">(+' +
this._result.Distance.toFixed(this.options.lengthUnit.decimal) +
")</div>";
} else {
text =
"<b>" +
this.options.angleUnit.label +
"</b> " +
this._result.Bearing.toFixed(this.options.angleUnit.decimal) +
" " +
this.options.angleUnit.display +
"<br><b>" +
this.options.lengthUnit.label +
"</b> " +
this._result.Distance.toFixed(this.options.lengthUnit.decimal) +
" " +
this.options.lengthUnit.display;
}
L.circleMarker(this._movingLatLong, this.options.circleMarker)
.bindTooltip(text, {
sticky: true,
offset: L.point(0, -40),
className: "moving-tooltip"
})
.addTo(this._tempPoint)
.openTooltip();
}
},
_escape: function (e) {
if (e.keyCode === 27) {
if (this._clickCount > 0) {
this._closePath();
} else {
this._choice = true;
this._toggleMeasure();
}
}
},
_calculateBearingAndDistance: function () {
var f1 = this._clickedLatLong.lat,
l1 = this._clickedLatLong.lng,
f2 = this._movingLatLong.lat,
l2 = this._movingLatLong.lng;
var toRadian = Math.PI / 180;
// haversine formula
// bearing
var y = Math.sin((l2 - l1) * toRadian) * Math.cos(f2 * toRadian);
var x =
Math.cos(f1 * toRadian) * Math.sin(f2 * toRadian) -
Math.sin(f1 * toRadian) *
Math.cos(f2 * toRadian) *
Math.cos((l2 - l1) * toRadian);
var brng =
Math.atan2(y, x) *
((this.options.angleUnit.factor
? this.options.angleUnit.factor / 2
: 180) /
Math.PI);
brng +=
brng < 0
? this.options.angleUnit.factor
? this.options.angleUnit.factor
: 360
: 0;
// distance
var R = this.options.lengthUnit.factor
? 6371 * this.options.lengthUnit.factor
: 6371; // kilometres
var deltaF = (f2 - f1) * toRadian;
var deltaL = (l2 - l1) * toRadian;
var a =
Math.sin(deltaF / 2) * Math.sin(deltaF / 2) +
Math.cos(f1 * toRadian) *
Math.cos(f2 * toRadian) *
Math.sin(deltaL / 2) *
Math.sin(deltaL / 2);
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
var distance = R * c;
this._result = {
Bearing: brng,
Distance: distance
};
},
_closePath: function () {
this._map.removeLayer(this._tempLine);
this._map.removeLayer(this._tempPoint);
if (this._clickCount <= 1) this._map.removeLayer(this._pointLayer);
this._choice = false;
L.DomEvent.on(this._container, "click", this._toggleMeasure, this);
this._toggleMeasure();
}
});
L.control.ruler = function (options) {
return new L.Control.Ruler(options);
};
}, window);
Codigo de styles.css
.leaflet-ruler {
height: 35px;
width: 35px;
background-image: url("./dist/icon.png"); /* <div>Icons made by <a href="http://www.freepik.com" title="Freepik">Freepik</a> from <a href="http://www.flaticon.com" title="Flaticon">www.flaticon.com</a> is licensed by <a href="http://creativecommons.org/licenses/by/3.0/" title="Creative Commons BY 3.0" target="_blank">CC 3.0 BY</a></div> */
background-repeat: no-repeat;
background-position: center;
}
.leaflet-ruler:hover {
background-image: url("./dist/icon-colored.png"); /* <div>Icons made by <a href="http://www.freepik.com" title="Freepik">Freepik</a> from <a href="http://www.flaticon.com" title="Flaticon">www.flaticon.com</a> is licensed by <a href="http://creativecommons.org/licenses/by/3.0/" title="Creative Commons BY 3.0" target="_blank">CC 3.0 BY</a></div> */
}
.leaflet-ruler-clicked {
height: 35px;
width: 35px;
background-repeat: no-repeat;
background-position: center;
background-image: url("./dist/icon-colored.png");
border-color: chartreuse !important;
}
.leaflet-bar {
background-color: #ffffff;
}
.leaflet-control {
cursor: pointer;
}
.result-tooltip {
background-color: white;
border-width: medium;
border-color: #de0000;
font-size: smaller;
}
.moving-tooltip {
background-color: rgba(255, 255, 255, 0.7);
background-clip: padding-box;
opacity: 0.5;
border: dotted;
border-color: red;
font-size: smaller;
}
.plus-length {
padding-left: 45px;
}
Codigo de leaflet - ruler.css
.leaflet-ruler {
height: 35px;
width: 35px;
background-image: url("./dist/icon.png"); /* <div>Icons made by <a href="http://www.freepik.com" title="Freepik">Freepik</a> from <a href="http://www.flaticon.com" title="Flaticon">www.flaticon.com</a> is licensed by <a href="http://creativecommons.org/licenses/by/3.0/" title="Creative Commons BY 3.0" target="_blank">CC 3.0 BY</a></div> */
background-repeat: no-repeat;
background-position: center;
}
.leaflet-ruler:hover {
background-image: url("./dist/icon-colored.png"); /* <div>Icons made by <a href="http://www.freepik.com" title="Freepik">Freepik</a> from <a href="http://www.flaticon.com" title="Flaticon">www.flaticon.com</a> is licensed by <a href="http://creativecommons.org/licenses/by/3.0/" title="Creative Commons BY 3.0" target="_blank">CC 3.0 BY</a></div> */
}
.leaflet-ruler-clicked {
height: 35px;
width: 35px;
background-repeat: no-repeat;
background-position: center;
background-image: url("./dist/icon-colored.png");
border-color: chartreuse !important;
}
.leaflet-bar {
background-color: #ffffff;
}
.leaflet-control {
cursor: pointer;
}
.result-tooltip {
background-color: white;
border-width: medium;
border-color: #de0000;
font-size: smaller;
}
.moving-tooltip {
background-color: rgba(255, 255, 255, 0.7);
background-clip: padding-box;
opacity: 0.5;
border: dotted;
border-color: red;
font-size: smaller;
}
.plus-length {
padding-left: 45px;
}
Crea una carpeta con el nombre dist y coloca los iconos de tu boton que va presentarse en el mapa