UNPKG

leaflet-freedraw

Version:

Zoopla inspired freehand polygon creation using Leaflet.js.

202 lines (145 loc) 6.67 kB
import { LineUtil, Point, Polygon, DomEvent } from 'leaflet'; import { defaultOptions, edgesKey, modesKey, polygons } from '../FreeDraw'; import { updateFor } from './Layer'; import createEdges from './Edges'; import { DELETE, APPEND } from './Flags'; import handlePolygonClick from './Polygon'; import concavePolygon from './Concave'; import mergePolygons from './Merge'; /** * @method appendEdgeFor * @param {Object} map * @param {Object} polygon * @param {Object} options * @param {Array} parts * @param {Object} newPoint * @param {Object} startPoint * @param {Object} endPoint * @return {void} */ const appendEdgeFor = (map, polygon, options, { parts, newPoint, startPoint, endPoint }) => { const latLngs = parts.reduce((accumulator, point, index) => { const nextPoint = parts[index + 1] || parts[0]; if (point === startPoint && nextPoint === endPoint) { return [ // We've found the location to add the new polygon. ...accumulator, map.containerPointToLatLng(point), map.containerPointToLatLng(newPoint) ]; } return [ ...accumulator, map.containerPointToLatLng(point) ]; }, []); // Update the lat/lngs with the newly inserted edge. polygon.setLatLngs(latLngs); // Remove the current set of edges for the polygon, and then recreate them, assigning the // new set of edges back into the polygon. polygon[edgesKey].map(edge => map.removeLayer(edge)); polygon[edgesKey] = createEdges(map, polygon, options); }; /** * @method createFor * @param {Object} map * @param {Array} latLngs * @param {Object} [options = defaultOptions] * @param {Boolean} [preventMutations = false] * @return {Array|Boolean} */ export const createFor = (map, latLngs, options = defaultOptions, preventMutations = false) => { // Determine whether we've reached the maximum polygons. const limitReached = polygons.get(map).size === options.maximumPolygons; // Apply the concave hull algorithm to the created polygon if the options allow. const concavedLatLngs = !preventMutations && options.concavePolygon ? concavePolygon(map, latLngs) : latLngs; // Simplify the polygon before adding it to the map. const addedPolygons = limitReached ? [] : map.simplifyPolygon(map, concavedLatLngs, options).map(latLngs => { const polygon = new Polygon(latLngs, { ...defaultOptions, ...options, className: 'leaflet-polygon' }).addTo(map); // Attach the edges to the polygon. polygon[edgesKey] = createEdges(map, polygon, options); // Disable the propagation when you click on the marker. DomEvent.disableClickPropagation(polygon); // Yield the click handler to the `handlePolygonClick` function. polygon.off('click'); polygon.on('click', handlePolygonClick(map, polygon, options)); return polygon; }); // Append the current polygon to the master set. addedPolygons.forEach(polygon => polygons.get(map).add(polygon)); if (!limitReached && !preventMutations && polygons.get(map).size > 1 && options.mergePolygons) { // Attempt a merge of all the polygons if the options allow, and the polygon count is above one. const addedMergedPolygons = mergePolygons(map, Array.from(polygons.get(map)), options); // Clear the set, and added all of the merged polygons into the master set. addedMergedPolygons.forEach(polygon => polygons.get(map).add(polygon)); return addedMergedPolygons; } return addedPolygons; }; /** * @method removeFor * @param {Object} map * @param {Object} polygon * @return {void} */ export const removeFor = (map, polygon) => { // Remove polygon and all of its associated edges. map.removeLayer(polygon); edgesKey in polygon && polygon[edgesKey].map(edge => map.removeLayer(edge)); // Remove polygon from the master set. polygons.get(map).delete(polygon); }; /** * @method clearFor * @param {Object} map * @return {void} */ export const clearFor = map => { Array.from(polygons.get(map).values()).forEach(polygon => removeFor(map, polygon)); }; /** * @param {Object} map * @param {Object} polygon * @param {Object} options * @return {Function} */ export default (map, polygon, options) => { return event => { // Gather all of the points from the lat/lngs of the current polygon. const newPoint = map.mouseEventToContainerPoint('originalEvent' in event ? event.originalEvent : event); const parts = polygon.getLatLngs()[0].map(latLng => map.latLngToContainerPoint(latLng)); const { startPoint, endPoint, lowestDistance } = parts.reduce((accumulator, point, index) => { const startPoint = point; const endPoint = parts[index + 1] || parts[0]; const distance = LineUtil.pointToSegmentDistance(newPoint, startPoint, endPoint); if (distance < accumulator.lowestDistance) { // If the distance is less than the previous then we'll update the accumulator. return { lowestDistance: distance, startPoint, endPoint }; } // Otherwise we'll simply yield the previous accumulator. return accumulator; }, { lowestDistance: Infinity, startPoint: new Point(), endPoint: new Point() }); // Setup the conditions for the switch statement to make the cases clearer. const mode = map[modesKey]; const isDelete = Boolean(mode & DELETE); const isAppend = Boolean(mode & APPEND); const isDeleteAndAppend = Boolean(mode & DELETE && mode & APPEND); // Partially apply the remove and append functions. const removePolygon = () => removeFor(map, polygon); const appendEdge = () => appendEdgeFor(map, polygon, options, { parts, newPoint, startPoint, endPoint }); switch (true) { // If both modes DELETE and APPEND are active then we need to do a little work to determine // which action to take based on where the user clicked on the polygon. case isDeleteAndAppend: lowestDistance > options.elbowDistance ? removePolygon() : appendEdge(); break; case isDelete: removePolygon(); break; case isAppend: appendEdge(); break; } // Trigger the event for having deleted a polygon or appended an edge. (isDelete || isAppend) && updateFor(map, isDelete ? 'remove' : 'append'); }; };