import React, { ReactElement, useState, useCallback } from 'react'; import { Story, Meta } from '@storybook/react/types-6-0'; import MapVEuMap, { MapVEuMapProps } from '../map/MapVEuMap'; import { BoundsViewport } from '../map/Types'; import Geohash from 'latlon-geohash'; import '../map/TempIconHack'; import BoundsDriftMarker, { BoundsDriftMarkerProps, } from '../map/BoundsDriftMarker'; import geohashAnimation from '../map/animation_functions/geohash'; import { defaultAnimationDuration } from '../map/config/map'; import { leafletZoomLevelToGeohashLevel } from '../map/utils/leaflet-geohash'; import { Viewport } from '../map/MapVEuMap'; export default { title: 'Map/Zoom animation', // component: MapVEuMap, } as Meta; const defaultAnimation = { method: 'geohash', animationFunction: geohashAnimation, duration: defaultAnimationDuration, }; // // when we implement the donut and histogram markers as DriftMarkers // maybe we can access the duration from context inside those components // in the meantime we will have to pass the duration into the getMarkerElements function // const getMarkerElements = ( { bounds, zoomLevel }: BoundsViewport, numMarkers: number, duration: number ) => { console.log( "I've been triggered with longitude bounds=[" + bounds.southWest.lng + ' TO ' + bounds.northEast.lng + '] and zoom=' + zoomLevel ); let aggsByGeohash = new Map(); // https://gist.github.com/mathiasbynens/5670917 // Here’s a 100% deterministic alternative to `Math.random`. Google’s V8 and // Octane benchmark suites use this to ensure predictable results. let myRandom = (function () { var seed = 0x2f6e2b1; return function () { // Robert Jenkins’ 32 bit integer hash function seed = (seed + 0x7ed55d16 + (seed << 12)) & 0xffffffff; seed = (seed ^ 0xc761c23c ^ (seed >>> 19)) & 0xffffffff; seed = (seed + 0x165667b1 + (seed << 5)) & 0xffffffff; seed = ((seed + 0xd3a2646c) ^ (seed << 9)) & 0xffffffff; seed = (seed + 0xfd7046c5 + (seed << 3)) & 0xffffffff; seed = (seed ^ 0xb55a4f09 ^ (seed >>> 16)) & 0xffffffff; return (seed & 0xfffffff) / 0x10000000; }; })(); let lats: number[] = []; let longs: number[] = []; const geohashLevel = leafletZoomLevelToGeohashLevel(zoomLevel); console.log(`geohashlevel ${geohashLevel}`); Array(numMarkers) .fill(undefined) .map(() => { // pick a deterministic point anywhere on the globe (hence a large value for numMarkers) let lat = -90 + myRandom() * 180; let long = -180 + myRandom() * 360; // move some points closer to a randomly picked previous point if (lats.length > 0 && myRandom() < 0.75) { const idx = Math.floor(myRandom() * lats.length); lat = lat + (lats[idx] - lat) * 0.999; long = long + (longs[idx] - long) * 0.999; } const south = bounds.southWest.lat; const north = bounds.northEast.lat; const west = bounds.southWest.lng; const east = bounds.northEast.lng; const lambda = 1e-8; // accommodate tiny rounding errors // is it within the viewport bounds? if ( lat > south && lat < north && (west < east - lambda ? long > west && long < east : west > east + lambda ? !(long > east && long < west) : true) ) { const geohash: string = Geohash.encode(lat, long, geohashLevel); let agg = aggsByGeohash.get(geohash); if (agg == null) { agg = { lat: 0, long: 0, latMin: undefined, latMax: undefined, longMin: undefined, longMax: undefined, count: 0, geohash, }; aggsByGeohash.set(geohash, agg); } agg.lat = agg.lat + lat; agg.long = agg.long + long; if (agg.latMin == null || lat < agg.latMin) agg.latMin = lat; if (agg.longMin == null || long < agg.longMin) agg.longMin = long; if (agg.latMax == null || lat > agg.latMax) agg.latMax = lat; if (agg.longMax == null || long > agg.longMax) agg.longMax = long; agg.count++; } lats.push(lat); longs.push(long); return undefined; }); return Array.from(aggsByGeohash.values()).map((agg) => { const meanLat = agg.lat / agg.count; const meanLong = agg.long / agg.count; const key = agg.geohash; return ( ); }); }; export const Default: Story = (args) => { const [markerElements, setMarkerElements] = useState< ReactElement[] >([]); const duration = defaultAnimationDuration; const handleViewportChanged = useCallback( (bvp: BoundsViewport) => { setMarkerElements(getMarkerElements(bvp, 100000, duration)); }, [setMarkerElements] ); // add setViewport and onViewportChanged to test showScale const [viewport, setViewport] = useState({ center: [20, -3], zoom: 5, }); const onViewportChanged: MapVEuMapProps['onViewportChanged'] = useCallback( ({ center, zoom }) => { if (center != null && center.length === 2 && zoom != null) { setViewport({ center: center, zoom: zoom }); } }, [setMarkerElements] ); return ( 4 ? true : false} /> ); }; Default.args = { showGrid: true, zoomLevelToGeohashLevel: leafletZoomLevelToGeohashLevel, height: '100vh', width: '100vw', }; interface DurationExtraProps { animationDuration: number; } export const DifferentSpeeds: Story = ( args ) => { const [markerElements, setMarkerElements] = useState< ReactElement[] >([]); const handleViewportChanged = useCallback( (bvp: BoundsViewport) => { setMarkerElements(getMarkerElements(bvp, 100000, args.animationDuration)); }, [setMarkerElements, args.animationDuration] ); const [viewport, setViewport] = useState({ center: [20, -3], zoom: 5, }); return ( ); }; DifferentSpeeds.args = { animationDuration: 2000, showGrid: true, zoomLevelToGeohashLevel: leafletZoomLevelToGeohashLevel, height: '100vh', width: '100vw', }; export const NoAnimation: Story = (args) => { const [markerElements, setMarkerElements] = useState< ReactElement[] >([]); const handleViewportChanged = useCallback( (bvp: BoundsViewport) => { setMarkerElements(getMarkerElements(bvp, 100000, 1)); }, [setMarkerElements] ); const [viewport, setViewport] = useState({ center: [20, -3], zoom: 5, }); return ( ); }; NoAnimation.args = { showGrid: true, zoomLevelToGeohashLevel: leafletZoomLevelToGeohashLevel, height: '100vh', width: '100vw', };