visgl / react-map-gl

React friendly API wrapper around MapboxGL JS
http://visgl.github.io/react-map-gl/
Other
7.78k stars 1.35k forks source link

[Bug] missing advanced example for geocoder #2353

Closed wibed closed 7 months ago

wibed commented 7 months ago

Description

https://github.com/visgl/react-map-gl/issues/771#issuecomment-489800371

along this comment, there is a common use to pass the mapRef to the geocoder instance as one could before the major update. now missing examples make it hard to figure out on how to approach this

Expected Behavior

-

Steps to Reproduce

-

Environment

Logs

No response

wibed commented 7 months ago

to put it into perspective. ive had a look at the source code from the old geocoder and it resulted in this:

a full example would be appreciated


  const getMapboxMap = () => {
    const { mapRef } = props
    return (mapRef && mapRef.current && mapRef.current.getMap()) || null
  }

  const getContainerNode = () => {
    const { containerRef } = props
    return (containerRef && containerRef.current) || null
  }

  const geocoder = useControl<MapboxGeocoder>(
    () => {

      const ctrl = new MapboxGeocoder({
        ...props,
        marker: false,
        accessToken: props.mapboxAccessToken,
        reverseGeocode: false
      });

      ctrl.on('clear', event => {
        onClear(event)
        setMarker(null)
      });

      ctrl.on('error', onError);

      ctrl.on('loading', event => {

        onLoading(event)

      });

      ctrl.on('results', onResults);

      ctrl.on('result', event => {

        onResult(event);

        const { result } = event;

        const location = result && (
          result.center || (result.geometry?.type === 'Point' && result.geometry.coordinates)
        );

        if (location) { setMarker(<Marker longitude={location[0]} latitude={location[1]} />) }
        else { setMarker(null) }
      });

      ctrl.on('error', onError);

      const mapboxMap = getMapboxMap()
      const containerNode = getContainerNode()

      if (mapboxMap && containerNode) {
        console.log("append")
        containerNode.appendChild(ctrl.onAdd(mapboxMap))
      }

      return ctrl;
    },
    {}
  );
wibed commented 7 months ago

i ended up with a working example. the culprit was me passing refs wrong.

here is my version, feel free to use it in any way you like.

import React, { forwardRef, useRef, useState, PropsWithChildren } from 'react';

import mapboxgl from 'mapbox-gl';
import ReactMapGL, { useControl, Marker } from 'react-map-gl';
import MapboxGeocoder, { GeocoderOptions } from '@mapbox/mapbox-gl-geocoder';

type GeocoderControlProps = Omit<GeocoderOptions, 'accessToken' | 'mapboxgl' | 'marker'> & {
  mapboxAccessToken: string;
  onMarker?: any; //boolean | Omit<MarkerProps, 'longitude' | 'latitude'>;

  position?: any; //ControlPosition;

  onClear?: (e: object) => void;
  onLoading?: (e: object) => void;
  onResults?: (e: object) => void;
  onResult?: (e: object) => void;
  onError?: (e: object) => void;
}

export const GeocoderControl = forwardRef(({
  ...props
}: GeocoderControlProps, ref: any) => {

  const { mapRef, containerRef } = ref

  const [marker, setMarker] = useState<any>(null);

  const {
    onClear = () => true,
    onResult = () => true,
    onError = () => true,
    onResults = () => true,
    onLoading = () => true
  } = props

  const getMapboxMap = () => {
    return (mapRef && mapRef.current && mapRef.current.getMap()) || null
  }

  const getContainerNode = () => {
    return (containerRef && containerRef.current) || null
  }

  const geocoder = useControl<MapboxGeocoder>(
    () => {

      const mapboxMap = getMapboxMap()
      const containerNode = getContainerNode()

      const ctrl = new MapboxGeocoder({
        ...props,
        marker: false,
        accessToken: props.mapboxAccessToken,
        reverseGeocode: false
      });

      if (mapboxMap && containerNode && !containerNode.hasChildNodes()) {
        containerNode.appendChild(ctrl.onAdd(mapboxMap))
      }

      ctrl.on('clear', event => {
        onClear(event)
        setMarker(null)
      });

      ctrl.on('error', onError);

      ctrl.on('loading', event => {
        onLoading(event)
      });

      ctrl.on('results', event => {
        onResults(event)
      });

      ctrl.on('result', event => {

        onResult(event);

        const { result } = event;

        const location = result && (
          result.center || (result.geometry?.type === 'Point' && result.geometry.coordinates)
        );

        if (location) { setMarker(<Marker longitude={location[0]} latitude={location[1]} />) }
        else { setMarker(null) }
      });

      ctrl.on('error', onError);

      return ctrl;
    }, {}
  );

  // @ts-ignore (TS2339) private member
  if (geocoder._map) {
    if (geocoder.getProximity() !== props.proximity && props.proximity !== undefined) {
      geocoder.setProximity(props.proximity);
    }
    if (geocoder.getRenderFunction() !== props.render && props.render !== undefined) {
      geocoder.setRenderFunction(props.render);
    }
    if (geocoder.getLanguage() !== props.language && props.language !== undefined) {
      geocoder.setLanguage(props.language);
    }
    if (geocoder.getZoom() !== props.zoom && props.zoom !== undefined) {
      geocoder.setZoom(props.zoom);
    }
    if (geocoder.getFlyTo() !== props.flyTo && props.flyTo !== undefined) {
      geocoder.setFlyTo(props.flyTo);
    }
    if (geocoder.getPlaceholder() !== props.placeholder && props.placeholder !== undefined) {
      geocoder.setPlaceholder(props.placeholder);
    }
    if (geocoder.getCountries() !== props.countries && props.countries !== undefined) {
      geocoder.setCountries(props.countries);
    }
    if (geocoder.getTypes() !== props.types && props.types !== undefined) {
      geocoder.setTypes(props.types);
    }
    if (geocoder.getMinLength() !== props.minLength && props.minLength !== undefined) {
      geocoder.setMinLength(props.minLength);
    }
    if (geocoder.getLimit() !== props.limit && props.limit !== undefined) {
      geocoder.setLimit(props.limit);
    }
    if (geocoder.getFilter() !== props.filter && props.filter !== undefined) {
      geocoder.setFilter(props.filter);
    }
    if (geocoder.getOrigin() !== props.origin && props.origin !== undefined) {
      geocoder.setOrigin(props.origin);
    }
    // Types missing from @types/mapbox__mapbox-gl-geocoder
    // if (geocoder.getAutocomplete() !== props.autocomplete && props.autocomplete !== undefined) {
    //   geocoder.setAutocomplete(props.autocomplete);
    // }
    // if (geocoder.getFuzzyMatch() !== props.fuzzyMatch && props.fuzzyMatch !== undefined) {
    //   geocoder.setFuzzyMatch(props.fuzzyMatch);
    // }
    // if (geocoder.getRouting() !== props.routing && props.routing !== undefined) {
    //   geocoder.setRouting(props.routing);
    // }
    // if (geocoder.getWorldview() !== props.worldview && props.worldview !== undefined) {
    //   geocoder.setWorldview(props.worldview);
    // }
  }

  return marker
})

// type FloorMapProps = Pick<PropsWithChildren> & {
//   initial: any;
//   onMove: any;
//   onResult: any;
//   onClear: any;
//   setSelection: any;
//   searchResult: any;
// }

export const FloorMap = forwardRef(({
  initial,
  onMove,
  onResult,
  onClear,
  setSelection,
  searchResult,
  // containerRef,
  viewportRef,
  ...props
}: any, ref: any) => {

  const { mapRef } = ref

  const settingPadding = 2
  const element = viewportRef
  const parent = element.parentNode
  const paddingTop = element.offsetTop
  const paddingLeft = element.offsetLeft
  const paddingRight = parent.offsetWidth - paddingLeft - element.offsetWidth - settingPadding
  const paddingBottom = parent.offsetHeight - paddingTop - element.offsetHeight - settingPadding

  // const { MAPBOX_PUBLIC_TOKEN, MAPBOX_STYLE_URL } = process.env

  if (!MAPBOX_PUBLIC_TOKEN) throw new Error("No public token for the maxbox api implemented")
  if (!MAPBOX_STYLE_URL) throw new Error("No style url for the mapbox api implemented")

  const [isSearch, setIsSearch] = useState<boolean | undefined>(false)

  const _onResult = (e) => {
    onResult(e)
    setIsSearch(undefined)
  }

  const _onClear = (e) => {
    onClear(e)
    setIsSearch(false)
  }

  return (
    <ReactMapGL
      ref={mapRef}
      onMove={(e) => onMove(e)}
      onLoad={(e) => {
        if (mapRef.current !== null) {
          const obj: any = mapRef.current
          if (obj.resize) {
            obj.easeTo({
              padding: {
                left: paddingLeft,
                top: paddingTop,
                right: paddingRight,
                bottom: paddingBottom
              }
            })

            obj.resize()
          }
        }
      }}
      mapStyle={`${MAPBOX_STYLE_URL}`}
      mapboxAccessToken={`${MAPBOX_PUBLIC_TOKEN}`}
      {...initial}
      width={'100%'}
      height={'100%'}
    >
      <style>{`
        .mapboxgl-map {
          position: absolute !important;
          top: 0;
          bottom: 0;
          width: 100% !important;
          height: 100% !important;
        }
        .mapboxgl-canvas {
          left: 0;
        }
        .mapboxgl-ctrl-logo, 
        .mapboxgl-ctrl-attrib, 
        .mapboxgl-ctrl-geocoder--powered-by {
          display: none !important;
        }
      `}</style>
      {mapRef && mapRef.current !== undefined
        &&

        < GeocoderControl
          ref={ref}
          mapboxAccessToken={MAPBOX_PUBLIC_TOKEN}
        />
      }

      {searchResult.map((el, idx) => (
        <div key={el.lon}>
          <Marker
            longitude={el.lon}
            latitude={el.lat}
          >
            <p
              className="h-6 w-6 cursor-pointer fill-white"
              onClick={() => setSelection(
                (prev) => prev === idx
                  ? undefined
                  : idx
              )}
            >
              <div>H</div>
            </p>
          </Marker>
        </div>
      ))}
    </ReactMapGL>
  )
})