UNPKG

letsee-geofencing

Version:

letsee geofencing npm package

619 lines (584 loc) 21.9 kB
import React, { useEffect, useRef, useState } from 'react' import { MapContainer, TileLayer, useMap, Marker, useMapEvents, Popup, LayersControl, FeatureGroup,Rectangle, Circle, LayerGroup, Polyline, Polygon } from 'react-leaflet' import axios from "axios" import { EditControl } from 'react-leaflet-draw' import circleToPolygon from 'circle-to-polygon' import './style.css' import './leaflet.contextmenu/leaflet.contextmenu.js' import './leaflet.contextmenu/leaflet.contextmenu.css' import delete_icon from './assets/delete_icon.jpeg' import change_icon from './assets/change_icon.png' // import 'leaflet-contextmenu' export const NamePopup = ({nameOrigin, setNameOrigin ,changeLayerName, updateFenceName, tmpEvent, setShowNamepopup}) => { const newName = useRef() const wrapperRef = useRef() const submit = async() => { changeLayerName(newName.current.value) await updateFenceName(tmpEvent.target, newName.current.value) // 2. namePopup 안보이기 setShowNamepopup(false) } const handleChange = (e) => { setNameOrigin(e.target.value) } const handleClickBackground = (e) => { if (e.target !== wrapperRef.current) return setShowNamepopup(false) } return ( <div className='name-popup-wrapper' onClick={handleClickBackground} ref={wrapperRef}> <div className='name-popup'> <p>설정하실 펜스의 이름을 입력하세요.</p> <input ref={newName} placeholder='name' value={nameOrigin} onChange={handleChange} /> <button onClick={submit}>변경</button> </div> </div> ); } export const GeofencingMap = ({geofencingWorker, initCenter, initZoom}) => { const [message, setMessage] = useState('') const [tmpEvent, setTempEvent] = useState(null) const [showNamepopup, setShowNamepopup] = useState(false) const [nameOrigin, setNameOrigin] = useState('') const drawnItems = useRef(); const map = useRef(); useEffect( () => { // 시작할때 // geofencingWorker.testLog() getFences() setTimeout(() => { map.current.off('contextmenu') }, 0); // const initFunction = async() => { // await getFences() // await map.current.off('contextmenu') // } // initFunction() return () => { } }, []) const changeLayerName = (newName) => { tmpEvent.target.geoName = newName tmpEvent.target.setTooltipContent(newName) } const _onCreated = async(event) => { addPolygon(event) } const _onEdited = async(event) => { console.log(event) const _layers = event.layers._layers for(let key in _layers){ generateBoundaryData(_layers[key]) } for(let key in _layers){ updateFenceGeog(_layers[key]) } } const _onDeleted = (event) => { const deletedLayers = event.layers._layers; removeFences(deletedLayers) } const removeFences = async(_layers) => { for(let key in _layers){ const res = await geofencingWorker.removeFence(_layers[key].geoId) } } const generate_uuidv4 = () => { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); return v.toString(16); }); } const addPolygon = (_event) => { const tmpId = generate_uuidv4() const tmpName = '마우스 오른쪽 클릭하여 이름을 설정하세요.' let tmpLayer = _event.layer const layerType = _event.layerType if(layerType==='circle'){ // console.log('converting Circle to Polygon...') const circleCenter = [tmpLayer._latlng.lng, tmpLayer._latlng.lat ] const createdPolygon = circleToPolygon(circleCenter, tmpLayer._mRadius, 32); tmpLayer.remove() // console.log(createdPolygon) let lngLatArray = [] createdPolygon.coordinates[0].forEach(el => { lngLatArray.push([el[1],el[0]]) }); let tmpPolygon = new L.Polygon([lngLatArray], { opacity: 0.5 }); tmpLayer = tmpPolygon } tmpLayer.geoId = tmpId tmpLayer.geoName = tmpName AddToolTip(tmpLayer, tmpLayer.geoName) generateBoundaryData(tmpLayer) addLayerClickEvent(tmpLayer) drawnItems.current.addLayer(tmpLayer) createFence(tmpLayer) } const createFence = async (_layer) => { if(_layer.hasOwnProperty('_rings')) // polygon { const res = await geofencingWorker.createFence(_layer,_layer.geoId,_layer.geoName) } } const updateFenceGeog = async (_layer) => { if(_layer.hasOwnProperty('_rings')) // polygon { const res = await geofencingWorker.updateFenceGeog(_layer,_layer.geoId) console.log(res) } } const updateFenceName = async (_layer, newName) => { // console.log('[ufn] layer :', _layer) // console.log('[ufn] newName :', newName) // console.log('[ufn] _layer.geoId :', _layer.geoId) const res = await geofencingWorker.updateFenceName(_layer.geoId, newName) } const addLayerClickEvent = (_Layer) => { // _Layer.on('click', (event) => { // 펜스 클릭 했을 때? // _Layer.on('contextmenu', (event) => { // 펜스 Right 클릭 했을 때? // setTempEvent(event) // const prevName = event.target.geoName // setNameOrigin(prevName) // // 1. namePopup 보이기 // setShowNamepopup(true) // }) _Layer.on('contextmenu', (event) => { // 펜스 Right 클릭 했을 때? let cm = map.current.contextmenu cm.event = event cm.showAt(event.latlng) // 우클릭 메뉴 보이기 }) } const getFences = async () => { const res = await geofencingWorker.getFences() if(!res.rows){console.log("Failed to load fences"); return;} res.rows.forEach(el => { const rowJson = JSON.parse(el.st_asgeojson) // console.log(el.id, el.name, rowJson.coordinates[0]) DrawPolygon(el.id, el.name, rowJson.coordinates[0]) }); // map.current.off('contextmenu') } const DrawPolygon = (id, name, coords) => { let tempCoords = [] for(let i=0; i<coords.length ; i++){ tempCoords.push( [coords[i][1],coords[i][0]] ) } let tmpPolygon = new L.Polygon(tempCoords, { opacity: 0.5} ); addLayerClickEvent(tmpPolygon) generateBoundaryData(tmpPolygon) tmpPolygon.geoId = id tmpPolygon.geoName = name AddToolTip(tmpPolygon, tmpPolygon.geoName) drawnItems.current.addLayer(tmpPolygon); } const generateBoundaryData = (_layer) => { let tmpArr = [] _layer._latlngs[0].forEach(el => { const tmpRow = [el.lng, el.lat] tmpArr.push(tmpRow) }); tmpArr.push([_layer._latlngs[0][0].lng, _layer._latlngs[0][0].lat]) _layer.boundaryData = { type : 'Polygon', coordinates : [tmpArr] } } const AddToolTip = (layer, name) => { layer.bindTooltip(name , {permanent: true, direction:"center"}).openTooltip() } const openNamePopup = (e) => { const cm = map.current.contextmenu const event = cm.event setTempEvent(event) const prevName = event.target.geoName setNameOrigin(prevName) // 1. namePopup 보이기 setShowNamepopup(true) } const func1 = () => { console.log("func1") } const func2 = () => { console.log("func2") } const func3 = () => { console.log("func3") } const removeClickedFence = async() => { console.log("4") const cm = map.current.contextmenu const event = cm.event const deletedLayer = event.target; drawnItems.current.removeLayer(deletedLayer) const res = await geofencingWorker.removeFence(deletedLayer.geoId) } const test = async() => { // console.log(map.current) // map.current.off('contextmenu') // map.current.contextmenu.removeAllItems() } return ( <> <div className='geofencingMap-wrapper'> {/* <button onClick={test}>PackageTest</button> */} { showNamepopup && <NamePopup setShowNamepopup={setShowNamepopup} nameOrigin={nameOrigin} setNameOrigin={setNameOrigin} changeLayerName={changeLayerName} updateFenceName={updateFenceName} tmpEvent={tmpEvent}></NamePopup> } <MapContainer contextmenu="true" contextmenuWidth= "140" contextmenuItems= {[{ text: '이름 변경', icon: change_icon, callback: openNamePopup }, '-', { text: '기능1', callback: func1 }, { text: '기능2', callback: func2 }, { text: '기능3', callback: func3 }, '-', { text: '삭제', icon: delete_icon, callback: removeClickedFence }]} ref={map} center={ initCenter? initCenter:[37.506012, 127.058175]} zoom={initZoom?initZoom:13} scrollWheelZoom={true} > <TileLayer attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors' url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" /> <LayersControl position="topright"> <LayersControl.Overlay checked="true" name="지오펜스 레이어"> <FeatureGroup ref={drawnItems}> <EditControl position='topleft' onCreated={_onCreated} onDeleted={_onDeleted} onEdited={_onEdited} draw={{ rectangle: false, polyline: false, circlemarker: false, marker: false, polygon:{ shapeOptions: { stroke: true, color: '#3388ff', weight: 4, opacity: 0.5, fill: true, clickable: true } }, circle:{ shapeOptions: { stroke: true, color: '#3388ff', weight: 4, opacity: 0.5, fill: true, clickable: true } } }} /> </FeatureGroup> </LayersControl.Overlay> </LayersControl> </MapContainer> </div> </> ) } export class GeofencingWorker{ constructor(SERVER_URL,MASTER_ID, PROJECT_ID ){ this.SERVER_URL = SERVER_URL this.MASTER_ID = MASTER_ID this.PROJECT_ID = PROJECT_ID // console.log(this.SERVER_URL, this.MASTER_ID, this.PROJECT_ID) this.createProject() } // setServerUrl (SERVER_URL) { // this.SERVER_URL = SERVER_URL // } // setMasterId (MASTER_ID) { // this.MASTER_ID = MASTER_ID // } // setProjectId (PROJECT_ID) { // this.PROJECT_ID = PROJECT_ID // } getFences = async () => { try { const res = await axios.get(this.SERVER_URL+'/getFences', { params: { sn: this.MASTER_ID+'_'+this.PROJECT_ID } }, {withCredentials: false}); if(res.data.results.name==="error"){ res.data.results.errorcode = 1; }else{ res.data.results.errorcode = 0; } return res.data.results } catch (error) { console.error(error) return error } } createFence = async (_layer, _fenceId, _fenceName) => { try{ const geom = "ST_GeomFromGeoJSON('"+JSON.stringify(_layer.boundaryData)+"')"; // const geom = "ST_GeomFromGeoJSON('"+_geoJson+"')"; const res = await axios.post(this.SERVER_URL+'/createFence', { sn: this.MASTER_ID+'_'+this.PROJECT_ID, fenceId: _fenceId, fenceName: _fenceName, fenceGeometry: geom }, { withCredentials: false, headers:{ "Content-Type": "application/x-www-form-urlencoded" } }) if(res.data.results.name==="error"){ res.data.results.errorcode = 1; }else{ res.data.results.errorcode = 0; } return res.data.results } catch (error) { return error } } updateFenceGeog = async (_layer, _fenceId) => { try{ const geom = "ST_GeomFromGeoJSON('"+JSON.stringify(_layer.boundaryData)+"')"; // const geom = "ST_GeomFromGeoJSON('"+_geoJson+"')"; const res = await axios.post(this.SERVER_URL+'/updateFenceGeog', { sn: this.MASTER_ID+'_'+this.PROJECT_ID, fenceId: _fenceId, fenceGeometry: geom }, { withCredentials: false, headers:{ "Content-Type": "application/x-www-form-urlencoded" } }) if(res.data.results.name==="error"){ res.data.results.errorcode = 1; }else{ res.data.results.errorcode = 0; } return res.data.results } catch (error) { return error } } updateFenceName = async (_fenceId, _newFenceName) => { try{ const res = await axios.post(this.SERVER_URL+'/updateFenceName', { sn: this.MASTER_ID+'_'+this.PROJECT_ID, fenceId: _fenceId, fenceName: _newFenceName, }, { withCredentials: false, headers:{ "Content-Type": "application/x-www-form-urlencoded" } }) if(res.data.results.name==="error"){ res.data.results.errorcode = 1; }else{ res.data.results.errorcode = 0; } return res.data.results } catch (error) { return error } } removeFence = async (_fenceId) => { try{ const res = await axios.post(this.SERVER_URL+'/removeFence', { sn: this.MASTER_ID+'_'+this.PROJECT_ID, fenceId: _fenceId, },{ withCredentials: false, headers:{ "Content-Type": "application/x-www-form-urlencoded" } }) if(res.data.results.name==="error"){ res.data.results.errorcode = 1; }else{ res.data.results.errorcode = 0; } return res.data.results } catch (error) { return error } } createProject = async () => { try{ const res = await axios.post(this.SERVER_URL+'/createProject', { sn: this.MASTER_ID+'_'+this.PROJECT_ID, }, { withCredentials: false, headers:{ "Content-Type": "application/x-www-form-urlencoded" } }); res.data.results.errorcode = 0 // return res.data.results return {'errorcode':0} } catch (error) { console.log(error) } } removeProject = async () => { // 구현 필요 // try{ // const res = await axios.post(this.SERVER_URL+'/removeProject', { // sn: this.MASTER_ID+'_'+this.PROJECT_ID, // }, // { // withCredentials: false, // headers:{ // "Content-Type": "application/x-www-form-urlencoded" // } // }); // res.data.results.errorcode = 0 // // return res.data.results // return {'errorcode':0} // } catch (error) { // console.log(error) // } } createObject = async (_clientId) => { try{ const res = await axios.get(this.SERVER_URL+'/createObject', { params: { sn: this.MASTER_ID+'_'+this.PROJECT_ID, lat: '127', lng: '-80', userId: _clientId } }, {withCredentials: false}) if(res.data.results.name==="error"){ res.data.results.errorcode = 1; } return res.data.results }catch{ } } updateObject = async (_clientId, _position) => { try { const res = await axios.get(this.SERVER_URL+'/updateObject', { params: { sn: this.MASTER_ID+'_'+this.PROJECT_ID, lat: _position.coords.latitude, lng: _position.coords.longitude, userId: _clientId } }, {withCredentials: false}); if(res.data.results.name==="error"){ res.data.results.errorcode = 1; }else{ res.data.results.errorcode = 0; } if(res.data.results[0].rowCount>0){ // _clientId 가 DB에 존재 한다면 if(res.data.results[1].rowCount>0){ // _clientId 의 위치가 어디엔가 속한다면 return res.data.results[1].rows }else{ return [] } }else{ this.createObject(_clientId) return this.updateObject(_clientId, _position) } } catch (error) { return error } }; getObjectHistory = async (_clientId) => { try{ const res = await axios.get(this.SERVER_URL+'/getObjectHistory', { params: { sn: this.MASTER_ID+'_'+this.PROJECT_ID, clientId: _clientId } }, {withCredentials: false}) return res.data.results.rows } catch (error) { return error } } testLog(){ console.log(this.SERVER_URL,this.MASTER_ID,this.PROJECT_ID) } } export class Client { constructor(geofencingWorker, id){ this.geofencingWorker = geofencingWorker this.id = id this.info = { count : 0, fences : [], position: null } this.watchId = null } startGPS = (insideCB, outsideCB, _options) => { let options = {} if(_options){ options = _options }else{ options = { enableHighAccuracy: true, maximumAge: 10000, timeout: 5000 } } if(navigator.geolocation){ this.watchId = navigator.geolocation.watchPosition( (position) => { this.updatePosition(position, insideCB, outsideCB) }, (err) => { console.log(err.message) }, options ) }else { console.log("Geolocation is not supported by this browser.") } } stopGPS = () => { navigator.geolocation.clearWatch(this.watchId); } updatePosition = async(_position, _insideCB, _outsideCB) => { // console.log(_position) const res = await this.geofencingWorker.updateObject(this.id, _position) this.info.count = res.length; this.info.fences = res; this.info.position = _position; if(res.length > 0){ if(_insideCB){ _insideCB( {count:res.length, fences:res, position:_position} ) } }else{ if(_outsideCB){ _outsideCB() } } } checkInfo = () => { return this.info } }