Wednesday, 1 July 2020

ionic react Here geofence

long press to add vertex to polygon
tap to check if location is inside geofence







//here7.js

import React, { useState, useEffect } from 'react';
import PageForm from './PageForm'

export default function Here() {
    const [map, setMap] = useState(null)
    const [layer, setLayer] = useState(null)

    useEffect(() => {
        getMap()
        setTimeout(() => {
            document.getElementById('refreshButton').click()
        }, 1000);
        return () => map.dispose();
    }, []);

    const H = window.H;

    const platform = new H.service.Platform({
        apikey: "JNIn_O9OQdca51JT5ofoou0WOKdp69bNG-XxHaHqPLo"
    });

    const defaultLayers = platform.createDefaultLayers();

    const getMap = () => {
        // Create an instance of the map
        const map = new H.Map(
            document.getElementById('mapView'),
            layer ? layer : defaultLayers.raster.normal.map,
            {
                // This map is centered over Europe
                zoom: 10,
                center: { lat: 51.048615, lng: -114.070847 },
                pixelRatio: window.devicePixelRatio || 1
            }
        );

        // Enable the event system on the map instance:
        const mapEvents = new H.mapevents.MapEvents(map);

        let markerNum = 1

        // Add longpress listeners:
        map.addEventListener('longpress', function (evt) {

            const coord = map.screenToGeo(evt.currentPointer.viewportX,
                evt.currentPointer.viewportY);
            console.log(coord.lat, coord.lng)

            //add a marker with number on left
            const svgMarkup = '<svg width="24" height="24" ' +
                'xmlns="http://www.w3.org/2000/svg">' +
                '<path d="M16.5 24.447v-0.996c3.352 0.099 5.993 1.174 5.993 2.487 0 1.379-2.906 2.56-6.492 2.56s-6.492-1.181-6.492-2.56c0-1.313 2.641-2.389 5.992-2.487v0.996c-2.799 0.069-4.993 0.71-4.993 1.491 0 0.827 2.459 1.623 5.493 1.623 3.033 0 5.492-0.796 5.492-1.623-0.001-0.781-2.194-1.421-4.993-1.491zM10.516 8.995c0-3.033 2.521-5.493 5.556-5.493 3.034 0 5.493 2.46 5.493 5.493 0 2.607-1.818 4.786-4.256 5.348l-1.309 13.219-1.313-13.256c-2.362-0.615-4.171-2.756-4.171-5.311zM16 7.524c0-0.828-0.671-1.498-1.498-1.498s-1.499 0.67-1.499 1.498c0 0.827 0.671 1.498 1.499 1.498s1.498-0.67 1.498-1.498z"></path>' +
                '<text x="6" y="18" font-size="12pt" font-family="Arial" font-weight="bold" ' +
                'text-anchor="middle" fill="black">${markerText}</text>' +
                '</svg>';

            const icon = new H.map.Icon(svgMarkup.replace('${markerText}', markerNum))
            const marker = new H.map.Marker(coord, { icon: icon })

            // add custom data to the marker
            marker.setData(markerNum);
            markerNum++

            map.addObject(marker);
        });

        let destinationMarker = null
        let createdPolygon = null


        // Add tap listeners:
        map.addEventListener('tap', function (evt) {

            const coord = map.screenToGeo(evt.currentPointer.viewportX,
                evt.currentPointer.viewportY);
            console.log(coord.lat, coord.lng)

            //remove last destination
            if (destinationMarker) { map.removeObject(destinationMarker) }

            //remove last polygon
            if (createdPolygon) { map.removeObject(createdPolygon) }

            //check if point is in geofence
            const pointInsdieGeoFence = geoFence(coord)
            if (pointInsdieGeoFence) { document.getElementById('geoFenceResult').innerHTML = 'Inside' }
            else { document.getElementById('geoFenceResult').innerHTML = 'Outside' }

            //draw polygon
            drawPolygon()

            //add a destination marker
            const svgMarkup = '<svg width="24" height="24" ' +
                'xmlns="http://www.w3.org/2000/svg">' +
                '<path d="M26 4v12h-16v12h-4v-24h20z"></path>' +
                '</svg>';

            const icon = new H.map.Icon(svgMarkup)
            destinationMarker = new H.map.Marker(coord, { icon: icon });

            map.addObject(destinationMarker);
        })

        const drawPolygon = () => {
            // get all objects added to the map
            var objects = map.getObjects(),
                len = map.getObjects().length,
                i;
            if (len < 3) { return }

            const vertexes = []
            // iterate over objects and calculate distance between them
            for (i = 0; i < len; i++) {
                vertexes.push(objects[i].b.lat)
                vertexes.push(objects[i].b.lng)
                vertexes.push(100)
            }

            vertexes.push(objects[0].b.lat)
            vertexes.push(objects[0].b.lng)
            vertexes.push(100)

            const lineString = new H.geo.LineString(
                vertexes,
                'values lat lng alt'
            );

            createdPolygon = map.addObject(
                new H.map.Polygon(lineString, {
                    style: {
                        fillColor: 'rgba(255, 238, 0, 0.7)',
                        strokeColor: 'rgba(55, 85, 170, 0.6)',
                        lineWidth: 2
                    }
                })
            );
        }

        const geoFence = (location) => {
            //get all vertexes
            var objects = map.getObjects(),
                len = map.getObjects().length,
                i, j, crossings = 0;
            if (len < 3) { return false }

            function rayCrossesSegment(point, a, b) {
                var px = point.lng,
                    py = point.lat,
                    ax = a.lng,
                    ay = a.lat,
                    bx = b.lng,
                    by = b.lat;
                if (ay > by) {
                    ax = b.lng;
                    ay = b.lat;
                    bx = a.lng;
                    by = a.lat;
                }
                // alter longitude to cater for 180 degree crossings
                if (px < 0) {
                    px += 360;
                }
                if (ax < 0) {
                    ax += 360;
                }
                if (bx < 0) {
                    bx += 360;
                }

                if (py == ay || py == by) py += 0.00000001;
                if ((py > by || py < ay) || (px > Math.max(ax, bx))) return false;
                if (px < Math.min(ax, bx)) return true;

                var red = (ax != bx) ? ((by - ay) / (bx - ax)) : Infinity;
                var blue = (ax != px) ? ((py - ay) / (px - ax)) : Infinity;
                return (blue >= red);

            }

            for (i = 0; i < len; i++) {
                j = i + 1;
                if (j >= len) {
                    j = 0;
                }

                if (rayCrossesSegment(location, objects[i].b, objects[j].b)) {
                    crossings++;
                }
            }

            // odd number of crossings?
            return (crossings % 2 == 1);
        }

        // Instantiate the default behavior, providing the mapEvents object:
        const behavior = new H.mapevents.Behavior(mapEvents);

        setMap(map)
    }

    const layerChange = async (selected) => {
        switch (selected) {
            case '1':
                await setLayer(defaultLayers.raster.normal.map)
                break
            case '2':
                await setLayer(defaultLayers.raster.normal.transit)
                break
            case '3':
                await setLayer(defaultLayers.raster.normal.mapnight)
                break
            case '4':
                await setLayer(defaultLayers.raster.normal.trafficincidents)
                break
            case '5':
                await setLayer(defaultLayers.raster.normal.xbase)
                break
            case '6':
                await setLayer(defaultLayers.raster.satellite.map)
                break
            case '7':
                await setLayer(defaultLayers.raster.satellite.xbase)
                break
            case '8':
                await setLayer(defaultLayers.raster.terrain.map)
                break
            case '9':
                await setLayer(defaultLayers.raster.terrain.xbase)
                break
            default:
                break
        }

        document.getElementById('refreshButton').click()
    }

    return (
        // Set a height on the map so it will display
        <div id='mapView' style={{ height: '100%' }}>
            <button id='refreshButton' onClick={() => { map.dispose(); getMap() }}
                style={{
                    position: 'fixed', top: '10px', left: '10px', zIndex: 2,
                    border: '2px solid green'
                }}
            >refresh</button>

            <select style={{
                position: 'fixed', top: '10px', left: '80px',
                height: '18px', width: '90px', zIndex: 2, fontSize: '13px'
            }}
                onChange={e => layerChange(e.target.value)}
            >
                <option value="1">default layer</option>
                <option value="2">transit</option>
                <option value="3">night</option>
                <option value="4">accident</option>
                <option value="5">xbase</option>
                <option value="6">satellite</option>
                <option value="7">satellite xbase</option>
                <option value="8">terrain</option>
                <option value="9">terrain xbase</option>
            </select>

            <button style={{ position: 'fixed', top: '10px', right: '10px', zIndex: 2 }}
                onClick={() => {
                    const formStyle = document.getElementById('pageForm').style
                    if (formStyle.display === 'none') {
                        formStyle.position = 'fixed'
                        formStyle.top = '30px'
                        formStyle.right = '10px'
                        formStyle.zIndex = 2
                        formStyle.display = 'block'
                        formStyle.backgroundColor = 'white'
                    }
                    else {
                        formStyle.display = 'none'
                    }
                }}>
                <i class="fa fa-bars"></i></button>

            <PageForm />

            <div style={{ position: 'fixed', top: '40px', left: '10px', zIndex: 2, backgroundColor: 'white' }}>
                Point is <i style={{ color: 'red' }} id='geoFenceResult'></i> GeoFence
            </div>
        </div>
    );

}

reference:
https://developer.here.com/blog/geofencing-regions-with-javascript-and-here
https://developer.here.com/documentation/maps/3.1.17.0/api_reference/H.service.extension.geofencing.Service.html

Ray-casting algorithm
https://gis.stackexchange.com/questions/42879/check-if-lat-long-point-is-within-a-set-of-polygons-using-google-maps
https://www.geeksforgeeks.org/how-to-check-if-a-given-point-lies-inside-a-polygon/#:~:text=1)%20Draw%20a%20horizontal%20line,true%2C%20then%20point%20lies%20outside.
1) Draw a horizontal line to the right of each point and extend it to infinity

1) Count the number of times the line intersects with polygon edges.

2) A point is inside the polygon if either count of intersections is odd or
   point lies on an edge of polygon.  If none of the conditions is true, then
   point lies outside.

Polygon on the Map
https://developer.here.com/documentation/examples/maps-js/geoshapes/polygon-on-the-map

No comments:

Post a Comment