How to calculate center and zoom for two coordinates to keep them visible

650 Views Asked by At

I'm working on project with your library and this is what I got

https://codesandbox.io/s/react-simple-maps-switching-topojson-on-zoom-forked-1s4qh?file=/src/index.js

import React, { useState, useRef, useEffect } from "react";
import { Spring, config, animated } from "react-spring";
import ReactDOM from "react-dom";

import {
  ComposableMap,
  Geographies,
  Geography,
  ZoomableGroup,
  Line,
  Point,
  Marker
} from "react-simple-maps";

const geoUrl =
  "https://raw.githubusercontent.com/zcreativelabs/react-simple-maps/master/topojson-maps/world-110m.json";

const MAX_ZOOM = 8;
const MIN_ZOOM = 1;

const calcZoom = (box, paddingPerc, width, height) => {
  const minXY = box[0];
  const maxXY = box[1];

  console.log(box);

  // find size of map area defined
  let zoomWidth = Math.abs(minXY[0] - maxXY[0]);
  let zoomHeight = Math.abs(minXY[1] - maxXY[1]);

  // increase map area to include padding
  zoomWidth = zoomWidth * (1 + paddingPerc / 100);
  zoomHeight = zoomHeight * (1 + paddingPerc / 100);

  // find scale required for area to fill svg
  const maxXscale = width / zoomWidth;
  const maxYscale = height / zoomHeight;
  const zoomScale = Math.min(maxXscale, maxYscale);

  // handle some edge cases
  // limit to max zoom (handles tiny countries)
  // zoomScale = Math.min(zoomScale, MAX_ZOOM);

  // // limit to min zoom (handles large countries and countries that span the date line)
  // zoomScale = Math.max(zoomScale, MIN_ZOOM);

  return zoomScale;
};

const AnimatedZoomableGroup = animated((props) => {
  return (
    <ZoomableGroup center={[props.lon, props.lat]} {...props}>
      {props.children}
    </ZoomableGroup>
  );
});

export const MapWidget = ({ from, to, width, height }) => {
  const [lat, setLat] = useState(0);
  const [lon, setLon] = useState(0);
  const [zoom, setZoom] = useState(1);

  useEffect(() => {
    if (
      from[0] !== null &&
      to[0] !== null &&
      from[1] !== null &&
      to[1] !== null
    ) {
      // [lon, lat]
      console.log(from, to);
      // const xMid = (from[0] + to[0]) / 2;
      const xMid = (to[0] - from[0]) / 2 + from[0];
      const yMid = (to[1] - from[1]) / 2 + from[1];
      // const yMid = (from[1] + to[1]) / 2;

      const minLon = Math.min(from[0], to[0]);
      const maxLon = Math.max(from[0], to[0]);

      const minLat = Math.min(from[1], to[1]);
      const maxLat = Math.max(from[1], to[1]);

      const bbox = [
        [minLon, maxLon],
        [minLat, maxLat]
      ];

      // setCenter(centroid);
      setLat(yMid);
      setLon(xMid);
      setZoom(calcZoom(bbox, 30, width, height));
    }
  }, [from, to]);

  return (
    <div style={{ width, height }}>
      <ComposableMap>
        <Spring
          from={{ animatedZoom: MIN_ZOOM, lat: 0, lon: 0 }}
          to={{ animatedZoom: zoom, lat: lat, lon: lon }}
          config={{ ...config.slow }}
        >
          {({ animatedZoom, lat, lon }) => (
            <AnimatedZoomableGroup
              maxZoom={MAX_ZOOM}
              minZoom={MIN_ZOOM}
              zoom={animatedZoom}
              lat={lat}
              lon={lon}
            >
              <Geographies
                geography={geoUrl}
                fill="#D6D6DA"
                stroke="#FFFFFF"
                strokeWidth={0.5}
              >
                {({ geographies }) =>
                  geographies.map((geo) => (
                    <Geography key={geo.rsmKey} geography={geo} />
                  ))
                }
              </Geographies>

              {from[0] && to[0] && (
                <Line
                  from={from}
                  to={to}
                  stroke="#FF5533"
                  strokeWidth={6 / zoom}
                  strokeLinecap="round"
                />
              )}

              {from && from[0] !== null && from[1] && (
                <Marker coordinates={from}>
                  <circle r={10 / zoom} fill="#F00" />
                </Marker>
              )}
              {to && to[0] !== null && to[1] && (
                <Marker coordinates={to}>
                  <circle r={10 / zoom} fill="#F00" />
                </Marker>
              )}
            </AnimatedZoomableGroup>
          )}
        </Spring>
      </ComposableMap>
    </div>
  );
};

const App = () => {
  const [from, setFrom] = useState([null, null]);
  const [to, setTo] = useState([null, null]);

  return (
    <div style={{ display: "flex" }}>
      <MapWidget from={from} to={to} width={400} height={200} />
      <button
        onClick={() => {
          setFrom([16.909270516076127, 52.41050505]);
          setTo([-54.9347238, -34.9391291]);
        }}
      >
        1
      </button>

      <button
        onClick={() => {
          setFrom([16.909270516076127, 52.41050505]);
          setTo([-5.6842457, 43.4545162]);
        }}
      >
        2
      </button>
    </div>
  );
};
const rootElement = document.getElementById("root");

ReactDOM.render(<App />, rootElement);

As you can see I'm trying to maximally zoom the map to show two points based on their coordinates

What is a problem, when you click on 2 the zooming work properly, but on 1 the map is zoomed to far - I'm trying to figure out what is wrong witch my calculation that it work only in some cases.

As you can see in function calcZoom I'm calculating zoom based on centroid of this two coordinates and bbox.

I'm basically trying to port this d3 example to react-simple-maps https://medium.com/@andybarefoot/making-a-map-using-d3-js-8aa3637304ee

If someone can help be would be greatfull.

Best!

0

There are 0 best solutions below