175 lines
4.9 KiB
React
175 lines
4.9 KiB
React
|
import { useLeafletContext } from '@react-leaflet/core';
|
||
|
import L from 'leaflet';
|
||
|
import markerIconPng from 'leaflet/dist/images/marker-icon.png';
|
||
|
import { useEffect, useContext } from 'react';
|
||
|
import { ApiContext } from '../../utils/api_context';
|
||
|
|
||
|
const userPositionBubble = {
|
||
|
color: 'black',
|
||
|
fillColor: 'black',
|
||
|
fillOpacity: 0.6,
|
||
|
weight: 5,
|
||
|
pmIgnore: true,
|
||
|
radius: 5,
|
||
|
};
|
||
|
|
||
|
const joinable = {
|
||
|
color: 'green',
|
||
|
weight: 1,
|
||
|
pmIgnore: true,
|
||
|
};
|
||
|
|
||
|
const unjoinable = {
|
||
|
color: 'red',
|
||
|
weight: 1,
|
||
|
pmIgnore: true,
|
||
|
};
|
||
|
|
||
|
const editable = {
|
||
|
color: 'blue',
|
||
|
weight: 1,
|
||
|
pmIgnore: false,
|
||
|
};
|
||
|
|
||
|
const icon = new L.Icon({ iconUrl: markerIconPng, iconSize: [25, 41], iconAnchor: [12, 41] });
|
||
|
|
||
|
const haversine = (p1, p2) => {
|
||
|
const degreesToRadians = (degrees) => degrees * (Math.PI / 180);
|
||
|
const delta = { lat: degreesToRadians(p2.lat - p1.lat), lng: degreesToRadians(p2.lng - p1.lng) };
|
||
|
const a =
|
||
|
Math.sin(delta.lat / 2) * Math.sin(delta.lat / 2) +
|
||
|
Math.cos(degreesToRadians(p1.lat)) *
|
||
|
Math.cos(degreesToRadians(p2.lat)) *
|
||
|
Math.sin(delta.lng / 2) *
|
||
|
Math.sin(delta.lng / 2);
|
||
|
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
||
|
const r = 6371 * 1000;
|
||
|
return r * c;
|
||
|
};
|
||
|
|
||
|
// GeoMan code is heavily adapted from this codesandbox: https://codesandbox.io/s/394eq
|
||
|
export const Geoman = ({ user, userPos, joinRoom }) => {
|
||
|
const context = useLeafletContext();
|
||
|
const api = useContext(ApiContext);
|
||
|
const circleAndMarkerFromChatroom = (chatRoom) => {
|
||
|
const circle = new L.Circle(chatRoom.center, chatRoom.radius);
|
||
|
const marker = new L.Marker(chatRoom.center, { pmIgnore: !chatRoom.isEditable, icon });
|
||
|
circle.setStyle(
|
||
|
chatRoom.isEditable
|
||
|
? editable
|
||
|
: haversine(userPos, { lat: chatRoom.latitude, lng: chatRoom.longitude }) < chatRoom.radius
|
||
|
? joinable
|
||
|
: unjoinable,
|
||
|
);
|
||
|
marker.addEventListener('click', () => {
|
||
|
console.log(chatRoom.id);
|
||
|
console.log(haversine(userPos, { lat: chatRoom.latitude, lng: chatRoom.longitude }), chatRoom.radius, userPos);
|
||
|
});
|
||
|
if (!!chatRoom.isEditable) {
|
||
|
[circle, marker].map((x) => {
|
||
|
x.on('pm:edit', (e) => {
|
||
|
const coords = e.target.getLatLng();
|
||
|
marker.setLatLng(coords);
|
||
|
circle.setLatLng(coords);
|
||
|
api.put(`/chat_rooms/${chatRoom.id}`, {
|
||
|
...chatRoom,
|
||
|
latitude: coords.lat,
|
||
|
longitude: coords.lng,
|
||
|
});
|
||
|
});
|
||
|
x.on('pm:remove', (e) => {
|
||
|
context.map.removeLayer(marker);
|
||
|
context.map.removeLayer(circle);
|
||
|
|
||
|
api.del(`/chat_rooms/${chatRoom.id}`);
|
||
|
});
|
||
|
});
|
||
|
circle.on('pm:drag', (e) => {
|
||
|
marker.setLatLng(e.target.getLatLng());
|
||
|
});
|
||
|
marker.on('pm:drag', (e) => {
|
||
|
circle.setLatLng(e.target.getLatLng());
|
||
|
});
|
||
|
}
|
||
|
[circle, marker].map((x) => x.addTo(context.map));
|
||
|
return [circle, marker];
|
||
|
};
|
||
|
|
||
|
const reRender = async () => {
|
||
|
const layersToRemove = [];
|
||
|
context.map.eachLayer((layer) => {
|
||
|
if (layer instanceof L.Circle || layer instanceof L.Marker) {
|
||
|
layersToRemove.push(layer);
|
||
|
}
|
||
|
});
|
||
|
|
||
|
const res = await api.get(`/chat_rooms?lat=${userPos.lat}&lng=${userPos.lng}`);
|
||
|
res.map((x) => {
|
||
|
circleAndMarkerFromChatroom({
|
||
|
center: [x.latitude, x.longitude],
|
||
|
...x,
|
||
|
isEditable: user && x.userId == user.id,
|
||
|
});
|
||
|
});
|
||
|
layersToRemove.map((x) => context.map.removeLayer(x));
|
||
|
|
||
|
const userLocationCircle = new L.Circle(userPos, 5);
|
||
|
userLocationCircle.setStyle(userPositionBubble);
|
||
|
userLocationCircle.addTo(context.map);
|
||
|
};
|
||
|
|
||
|
useEffect(() => {
|
||
|
if (context) {
|
||
|
reRender();
|
||
|
}
|
||
|
}, [userPos]);
|
||
|
|
||
|
useEffect(() => {
|
||
|
const leafletContainer = context.layerContainer || context.map;
|
||
|
leafletContainer.pm.addControls({
|
||
|
drawMarker: false,
|
||
|
editControls: true,
|
||
|
dragMode: true,
|
||
|
cutPolygon: false,
|
||
|
removalMode: true,
|
||
|
rotateMode: false,
|
||
|
splitMode: false,
|
||
|
drawPolyline: false,
|
||
|
drawRectangle: false,
|
||
|
drawPolygon: false,
|
||
|
drawCircleMarker: false,
|
||
|
});
|
||
|
|
||
|
leafletContainer.pm.setGlobalOptions({ pmIgnore: false });
|
||
|
|
||
|
leafletContainer.on('pm:create', async (e) => {
|
||
|
if (e.layer && e.layer.pm) {
|
||
|
const shape = e;
|
||
|
context.map.removeLayer(shape.layer);
|
||
|
|
||
|
const { lat: latitude, lng: longitude } = shape.layer.getLatLng();
|
||
|
const chatRoom = await api.post('/chat_rooms', {
|
||
|
latitude,
|
||
|
longitude,
|
||
|
radius: shape.layer.getRadius(),
|
||
|
});
|
||
|
reRender();
|
||
|
}
|
||
|
});
|
||
|
|
||
|
leafletContainer.on('pm:remove', (e) => {
|
||
|
console.log('object removed');
|
||
|
// console.log(leafletContainer.pm.getGeomanLayers(true).toGeoJSON());
|
||
|
});
|
||
|
|
||
|
return () => {
|
||
|
leafletContainer.pm.removeControls();
|
||
|
leafletContainer.pm.setGlobalOptions({ pmIgnore: true });
|
||
|
};
|
||
|
}, [context]);
|
||
|
|
||
|
return null;
|
||
|
};
|
||
|
|
||
|
export default Geoman;
|