alex3165 / react-mapbox-gl

A React binding of mapbox-gl-js
http://alex3165.github.io/react-mapbox-gl/
MIT License
1.93k stars 535 forks source link

Get Features when move a marker #866

Open jadelmag opened 4 years ago

jadelmag commented 4 years ago

I'm trying to move a marker and drop marker only when under marker exists previously layer aded.

The problem is that sometimes it does not detect the layer below or when I change the zoom, the positions do not correspond between them.

I have tried using turf functions to detect if the marker is inside the line or touches the line but it doesn't work.

It is as if when zooming the layer is displayed well but changes the position of the lines, because I pass the mark where the line is drawn and does not return anything but I move it to other areas where there is nothing drawn and if it returns that the layer is underneath.

What am doing wrong?? How can I solve it??

Here is an example: https://codesandbox.io/s/infallible-wood-d49yk?file=/src/Map.js

import React, { ReactNode } from "react";
import InteractiveMap, {
    Marker,
    InteractiveMapProps,
    ViewportProps,
    MapLoadEvent,
} from "react-map-gl";
import { injectIntl, WrappedComponentProps } from "react-intl";

// Necesary Methods
// import booleanPointOnLine from "@turf/boolean-point-on-line";
// @ts-ignore
// import booleanWithin from "@turf/boolean-within";
// import { point } from "@turf/helpers";

import FlowMeterMenu from "./FlowMeterMenu";
import GeojsonPipieLayer from "./GeojsonPipeLayer";
import GeojsonPointLayer from "./GeojsonPointLayer";
import FlowMiter from "./Icons/FlowMiter";

import { lightTheme, darkTheme } from "../../../constants";
import { GEOJSON_TITLE_LAYER } from "./constants";
import { mapThemes } from "../../../utils/mods";

import {
    updateFlowMeterMenu,
    flowMeterUpdatePosition,
    disableMenuAllFlowMeters,
} from "./helpers";

import {
    ISettingsMapProps,
    ISettingsMapState,
    IFlowMeter,
    IDragEvent,
    Center,
    IPointLayer,
} from "./interfaces";

export class SettingsMap extends React.PureComponent<
    Readonly<
        ISettingsMapProps &
            InteractiveMapProps &
            WrappedComponentProps & { children?: ReactNode }
    >,
    ISettingsMapState
> {
    mapRef: React.RefObject<InteractiveMap> = React.createRef<InteractiveMap>();
    timeout: ReturnType<typeof setTimeout> = setTimeout(() => "", 1000);

    constructor(
        props: Readonly<
            ISettingsMapProps &
                InteractiveMapProps &
                WrappedComponentProps & { children?: ReactNode }
        >
    ) {
        super(props);
        this.state = {
            viewport: {
                width: 100,
                height: 100,
                latitude: 39.46096,
                longitude: -0.40212,
                zoom: 12,
                pitch: 0,
            },
            mapGL: undefined,
            interactiveLayers: [GEOJSON_TITLE_LAYER],
        };
    }

    componentDidUpdate(prevProps: ISettingsMapProps) {
        if (prevProps.showModal !== this.props.showModal) {
            const { mapGL } = this.state;
            mapGL?.getMap().on("moveend", () => {
                this.updateViewportOnFinishFly(mapGL);
            });
        }
    }

    componentWillUnmount(): void {
        clearTimeout(this.timeout);
        const { mapGL } = this.state;
        if (mapGL) {
            mapGL?.getMap().off("moveend");
        }
    }

    updateViewportOnFinishFly = (mapGL: any) => {
        const { viewport } = this.state;
        mapGL.getMap().off("moveend");
        const center: Center = mapGL
            ?.getMap()
            .getCenter()
            .wrap();
        const zoom: number = mapGL?.getMap().getZoom();
        const updatedViewport = Object.assign({}, viewport, {
            latitude: center.lat,
            longitude: center.lng,
            zoom: zoom,
        });
        this.setState({ viewport: updatedViewport });
    };

    onClickedMap = (): void => {
        const { flowmeters, setFlowMeters } = this.props;
        const newArray: Array<IFlowMeter> = disableMenuAllFlowMeters(
            flowmeters
        );
        setFlowMeters(newArray);
    };

    handleLoad = (event: MapLoadEvent): void => {
        const loaded: boolean = event.target.loaded();
        const mapGL = this.mapRef.current;
        if (this.props.onSetLoaded) {
            this.props.onSetLoaded(loaded, mapGL);
        }
        if (mapGL !== null) {
            this.setState({ mapGL: mapGL });
        }
    };

    onClickedFlowmeter = (flowmeter: IFlowMeter): void => {
        const { flowmeters, setFlowMeters } = this.props;
        const newArray: Array<IFlowMeter> = updateFlowMeterMenu(
            flowmeter,
            flowmeters,
            true
        );
        setFlowMeters(newArray);
    };

    onDrag = (event: any) => {
        const point: number[] = [event.center.x, event.center.y];
        this.checkPointOnLayer(point);
    }

    onDragEnd = (event: IDragEvent, flowmeter: IFlowMeter): void => {
        const { flowmeters, setFlowMeters } = this.props;
        const point: number[] = [event.center.x, event.center.y];
        const pointOnLayer: IPointLayer = this.checkPointOnLayer(point);
        if (!pointOnLayer.status) return;
        const lngLat: number[] = event.lngLat;
        const newArray: Array<IFlowMeter> = flowMeterUpdatePosition(
            pointOnLayer.info, // change lngLat by pipe id
            lngLat,
            flowmeter,
            flowmeters
        );
        setFlowMeters(newArray);
    };

    checkPointOnLayer = (point: number[]): IPointLayer => {
        const { mapGL } = this.state;
        const features = mapGL?.getMap().queryRenderedFeatures(point);
        let isPointOnLayer: IPointLayer = { status: false, info: undefined };
        features.forEach((mapboxFeature: any) => {
            if (mapboxFeature.layer.id.startsWith(GEOJSON_TITLE_LAYER)) {
                console.log("marker is in pipe layer!!: ", mapboxFeature);
                isPointOnLayer = { status: true, info: mapboxFeature };
            }
        });
        return isPointOnLayer;
    };

    renderMarker = (flowmeter: IFlowMeter, index: number) => {
        const { flowmeters, setFlowMeters } = this.props;
        const { showUpdateModal } = this.props;
        return (
            <div
                className="flowmeter-marker-compose"
                key={index}
                onMouseUp={() => {
                    if (showUpdateModal) return;
                    this.onClickedFlowmeter(flowmeter);
                }}
            >
                <Marker
                    className={`flowmeter-marker ${
                        showUpdateModal ? "--disabled" : "--enabled"
                    }`}
                    draggable={flowmeter.draggable}
                    latitude={flowmeter.latitude}
                    longitude={flowmeter.longitude}
                    onDrag={this.onDrag}
                    onDragEnd={(event: any) => {
                        if (showUpdateModal) return;
                        this.onDragEnd(event, flowmeter);
                    }}
                >
                    {flowmeter && flowmeter.menu && (
                        <FlowMeterMenu
                            flowmeter={flowmeter}
                            flowmeters={flowmeters}
                            updateFlowMeters={setFlowMeters}
                        />
                    )}
                    <FlowMiter />
                </Marker>
            </div>
        );
    };

    renderPipeLayer = (
        features:
            | string
            | GeoJSON.Feature<GeoJSON.Geometry, GeoJSON.GeoJsonProperties>
            | GeoJSON.FeatureCollection<
                  GeoJSON.Geometry,
                  GeoJSON.GeoJsonProperties
              >
            | undefined
    ) => {
        return <GeojsonPipieLayer data={features} />;
    };

    renderPointsLayer = (
        features:
            | string
            | GeoJSON.Feature<GeoJSON.Geometry, GeoJSON.GeoJsonProperties>
            | GeoJSON.FeatureCollection<
                  GeoJSON.Geometry,
                  GeoJSON.GeoJsonProperties
              >
            | undefined
    ) => {
        return <GeojsonPointLayer data={features} />;
    };

    render() {
        const { theme, children, showModal, flowmeters } = this.props;
        const { width, height, geojson } = this.props;
        const { viewport, interactiveLayers } = this.state;
        const mapStyle: string =
            theme === mapThemes.DARK ? darkTheme : lightTheme;
        return (
            <div className="dma-map__wrapper">
                <InteractiveMap
                    {...viewport}
                    ref={this.mapRef}
                    className="mapbox"
                    width={width}
                    height={height}
                    longitude={viewport.longitude}
                    latitude={viewport.latitude}
                    mapStyle={mapStyle}
                    attributionControl={false}
                    onLoad={this.handleLoad}
                    onClick={this.onClickedMap}
                    onViewportChange={(viewport: ViewportProps) => {
                        viewport.width = 100;
                        viewport.height = 100;
                        this.setState({ viewport });
                    }}
                    interactiveLayerIds={geojson ? interactiveLayers : []}
                >
                    {!showModal && children}
                    {!showModal && geojson && this.renderPipeLayer(geojson)}
                    {!showModal && geojson && this.renderPointsLayer(geojson)}

                    {!showModal &&
                        flowmeters &&
                        flowmeters.length > 0 &&
                        flowmeters.map(this.renderMarker)}
                </InteractiveMap>
            </div>
        );
    }
}

export default injectIntl(SettingsMap);
ScottEAdams commented 4 years ago

I think you may have posted in the wrong repo. This is for react-mapbox-gl not react-map-gl.