Arnab
Arnab

Reputation: 418

Leaflet coordinate to lat lng in responsive design

I have a Leaflet responsive map, which I am using in react. My map is a custom floor plan and it is in svg. I have setup the map with "ImageOverlay" and everything is working fine. Now my problem is happening only when I am using point (x, y) coordinates and converting it to lat, lng. The lat, lng is changing based on screen size. I need a fixed lat, lng respective of the screen size so that I can show a marker. Currently if I show a marker based on the screen size the marker position is showing at different places. So my question is how to get lat, lng irrespective of the screen size? Here is my code:

L.CRS.Simple = L.extend({}, L.CRS, {
  projection: L.Projection.LonLat,
  transformation: new L.Transformation(1, 0, 1, 0)
});

const mapHeight = 400;
const mapWidth = 100;
const svgImageWidth = 2245;
const svgImageHeight = 1587;

const initState = {
  mapBounds: [[0, 0], [1000, 1000]],
  mapMarkerPosition: [40.751, -74.17],
  mapZoom: 4,
  mapCenter: [40.74400563300867, -74.1680145263672],
  xCoordinate: "",
  yCoordinate: "",
  coordinates: { x: 292, y: 89 }
};

export default function App() {
  const mapRef = useRef(null);
  const [state, setState] = useState(initState);

  function getBoundsUnproject(map) {
    const southWest = map.unproject([0, svgImageHeight], 6);
    const northEast = map.unproject([svgImageWidth, 0], 6);
    const bounds = new L.LatLngBounds(southWest, northEast);
    map.setMaxBounds(bounds);
    return { bounds };
  }

  React.useEffect(() => {
    const map = mapRef.current.leafletElement;
    if (map) {
      const { bounds } = getBoundsUnproject(map);
      console.log("bounds", bounds);
      map.fitBounds(bounds);

      const centerLatLng = map.getCenter();
      console.log("center", centerLatLng);

      const customCoordinates = state.coordinates;
      // console.log("customCoordinates", customCoordinates);
      const point = L.point(customCoordinates.x, customCoordinates.y);
      const customCoordinatesLatLng = map.containerPointToLatLng(point);
      console.log("customCoordinatesLatLng", customCoordinatesLatLng);

      setState(prevState => ({
        ...prevState,
        mapBounds: [
          [bounds._northEast.lat, bounds._northEast.lng],
          [bounds._southWest.lat, bounds._southWest.lng]
        ],
        mapMarkerPosition: [
          customCoordinatesLatLng.lat,
          customCoordinatesLatLng.lng
        ]
      }));
    }
  }, [state.coordinates]);

  function handleChange(event) {
    const name = event.target.name;
    const value = event.target.value;

    if (isNaN(value)) {
      return;
    }

    setState(prevState => ({
      ...prevState,
      [name]: value
    }));
  }

  function handleClick() {
    setState(prevState => ({
      ...prevState,
      coordinates: { x: state.xCoordinate, y: state.yCoordinate }
    }));
  }

  return (
    <div className="container-fluid">
      <div className="row justify-content-center mt-5">
        <div className="col-md-6">
          <div className="row justify-content-center">
            <div className="col-md-4">
              <input
                style={{ width: "100%" }}
                type="text"
                value={state.xCoordinate}
                onChange={handleChange}
                name="xCoordinate"
                placeholder="x coordinate (default: 292)"
              />
            </div>
            <div className="col-md-4">
              <input
                style={{ width: "100%" }}
                type="text"
                value={state.yCoordinate}
                onChange={handleChange}
                name="yCoordinate"
                placeholder="y coordinate (default: 89)"
              />
            </div>
            <div className="col-md-2">
              <button onClick={handleClick}>Submit</button>
            </div>
          </div>
        </div>
      </div>
      <div className="row justify-content-center mt-5 mb-5">
        <div className="col-6">
          <LeafletMap
            ref={mapRef}
            crs={L.CRS.Simple}
            center={state.mapCenter}
            zoom={state.mapZoom}
            minZoom={state.mapZoom}
            style={{ height: `${mapHeight}px`, width: `${mapWidth}%` }}
          >
            <ImageOverlay
              bounds={state.mapBounds}
              url={"image-url"}
            >
              <Marker
                position={state.mapMarkerPosition}
                icon={customMarkerIcon}
              />
            </ImageOverlay>
          </LeafletMap>
        </div>
      </div>
    </div>
  );
}


It would be helpful if someone points out what am I doing wrong.

Thanks.

Upvotes: 2

Views: 923

Answers (1)

ghybs
ghybs

Reputation: 53320

The issue is that map.containerPointToLatLng(), as its name implies, assumes that the Point you give to it is relative to the map's container coordinates, i.e. its top left corner.

But since we usually specify a map view by its center (and zoom), the container top left may show a different lat-lng position depending on its size. And since you set its size (width) as 100% of its parent element, you have this size potentially changing with screen size / orientation, if you have a responsive design.

And when the container top left corner shows a different lat-lng position, map.containerPointToLatLng will also differ.

A possible solution is simply to remain consistent in how you compute your conversions. You very probably actually want your Marker to be positioned relatively to your SVG floor plan, rather than the map container. In that case, simply compute your Marker coordinates the same way you do for the bounds of your floor plan, i.e. with map.unproject()

Upvotes: 2

Related Questions