diff --git a/src/App.tsx b/src/App.tsx index b04ca45..93f0554 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -29,8 +29,6 @@ function App() { const leftMapRef = useRef(); const rightMapRef = useRef(); - - // State variables const [backgroundBasemapStyle, setBackgroundBasemapStyle] = useState( // "satellite-streets-v12" @@ -38,6 +36,11 @@ function App() { version: 8, sources: {}, layers: [], glyphs: "mapbox://fonts/mapbox/{fontstack}/{range}.pbf" } ); + const [customPlanetApiKey, setCustomPlanetApiKey] = useLocalStorage( + "customPlanetApiKey", + import.meta.env.VITE_PLANET_BASEMAP_API_KEY + ); + // const backgroundBasemapStyle = "satellite-streets-v12"; // const [leftTimelineDate, setLeftTimelineDate] = useState( // subMonths(new Date(), 1) @@ -45,13 +48,6 @@ function App() { // const [rightTimelineDate, setRightTimelineDate] = useState( // subMonths(new Date(), 18) // ); - - const [customPlanetApiKey, setCustomPlanetApiKey] = useLocalStorage( - "customPlanetApiKey", - import.meta.env.VITE_PLANET_BASEMAP_API_KEY - ); - - const [leftTimelineDate, setLeftTimelineDate] = useLocalStorage( "leftTimelineDate", subMonths(new Date(), 2), @@ -71,13 +67,11 @@ function App() { // console.log(leftTimelineDate, 'leftTimelineDate', rightTimelineDate, 'rightTimelineDate') setleftPlanetUrl(planetBasemapUrl(leftTimelineDate, customPlanetApiKey)); }, [customPlanetApiKey, leftTimelineDate]); - useEffect(() => { // console.log(leftTimelineDate, 'leftTimelineDate', rightTimelineDate, 'rightTimelineDate') setRightPlanetUrl(planetBasemapUrl(rightTimelineDate, customPlanetApiKey)); }, [customPlanetApiKey, rightTimelineDate]); - // Like Mapbox gl compare https://docs.mapbox.com/mapbox-gl-js/example/mapbox-gl-compare/ // const [splitPanelSizesPercent, setSplitPanelSizesPercent] = useState([75, 25]); const [splitPanelSizesPercent, setSplitPanelSizesPercent] = useLocalStorage( @@ -85,8 +79,10 @@ function App() { [75, 25] ); - const { url: leftWaybackUrl, loading: leftLoading } = useWaybackUrl(leftTimelineDate); - const { url: rightWaybackUrl, loading: rightLoading } = useWaybackUrl(rightTimelineDate); + const [waybackItemsWithLocalChanges, setWaybackItemsWithLocalChanges] = useState([]); + const { url: leftWaybackUrl, loading: leftLoading } = useWaybackUrl(leftTimelineDate, waybackItemsWithLocalChanges); + const { url: rightWaybackUrl, loading: rightLoading } = useWaybackUrl(rightTimelineDate, waybackItemsWithLocalChanges); + const [viewState, setViewState] = useState({ zoom: 3, @@ -482,7 +478,8 @@ function App() { type="raster" tiles={[leftWaybackUrl]} tileSize={256} - key={"wayback" + leftTimelineDate.toString().toLowerCase()} + // key={"wayback" + leftTimelineDate.toString().toLowerCase()} + key={"wayback-" + leftWaybackUrl.split('/').reverse()[3]} > @@ -563,7 +560,8 @@ function App() { type="raster" tiles={[rightWaybackUrl]} tileSize={256} - key={"wayback" + rightTimelineDate.toString().toLowerCase()} + // key={"wayback" + rightTimelineDate.toString().toLowerCase()} + key={"wayback-" + rightWaybackUrl.split('/').reverse()[3]} > @@ -639,6 +637,7 @@ function App() { setLeftSelectedTms={setLeftSelectedTms} setRightSelectedTms={setRightSelectedTms} leftMapRef={leftMapRef} + setWaybackItemsWithLocalChanges={setWaybackItemsWithLocalChanges} /> ); diff --git a/src/control-panel.tsx b/src/control-panel.tsx index 2bd0413..3555c1a 100644 --- a/src/control-panel.tsx +++ b/src/control-panel.tsx @@ -8,8 +8,8 @@ import { LngLatBounds, LngLat } from "mapbox-gl"; // import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider"; // import PropTypes from 'prop-types' -import { - getWaybackItemsWithLocalChanges, +import { + getWaybackItemsWithLocalChanges, // WaybackItem } from '@vannizhang/wayback-core'; import { @@ -19,7 +19,7 @@ import { import PlayableSlider from "./playable-slider"; import LinksSection from "./links-section"; -import {ExportSplitButton, ExportButtonOptions} from "./export-split-button"; +import { ExportSplitButton, ExportButtonOptions } from "./export-split-button"; import SettingsModal from "./settings-modal"; import { toPng } from 'html-to-image'; @@ -34,9 +34,9 @@ import { Stack, IconButton, Divider, - Box, - Drawer, - Slider, + Box, + Drawer, + Slider, Checkbox } from "@mui/material"; @@ -92,14 +92,14 @@ const escapeTmsUrl = (url: string) => .replace("{y}", "${y}") .replace("{z}", "${z}") .replace("{quadkey}", "${quadkey}") - .replaceAll("&", "&"); + .replaceAll("&", "&"); // const unescapeTmsUrl = (url: string) => // url.replace("${x}", "{x}").replace("${y}", "{y}").replace("${z}", "{z}"); function buildGdalWmsXml(tmsUrl: string) { - return (!tmsUrl.includes('quadkey')) ? - `${escapeTmsUrl( tmsUrl )}-20037508.3420037508.3420037508.34-20037508.341811topEPSG:38572562563` - : - `${escapeTmsUrl( tmsUrl )}4`; + return (!tmsUrl.includes('quadkey')) ? + `${escapeTmsUrl(tmsUrl)}-20037508.3420037508.3420037508.34-20037508.341811topEPSG:38572562563` + : + `${escapeTmsUrl(tmsUrl)}4`; } // See discussion here https://github.com/developmentseed/titiler/discussions/640 @@ -116,7 +116,7 @@ function titilerCropUrl( // const ll_3857 = convertLatlonTo3857(bounds.getSouthWest()); // const ur_3857 = convertLatlonTo3857(bounds.getNorthEast()); // const coords_str = `${ll_3857.x},${ll_3857.y},${ur_3857.x},${ur_3857.y}.tif?max_size=${MAX_FRAME_SIZE}&coord-crs=epsg:3857`; // 3857 - + // pre 0.15.0, endpoint is /cog/crop/ like on app.ico titiler endpoint at commit day // post 0.15.0 included, endpoint is /cog/bbox/ const bbox_crop_endpoint = titilerEndpoint.toLowerCase().includes('app.iconem') ? 'crop' : 'bbox'; @@ -171,13 +171,13 @@ async function fetchTitilerFramesBatches(gdalTranslateCmds: any, aDiv: any) { // ----------------------------------------------------- // Mini-Components only used in ControlPanel // ------------------------------------------------------ -const OpacitySlider = (props:any) => { - const handleOpacityChange = (event:any) => { +const OpacitySlider = (props: any) => { + const handleOpacityChange = (event: any) => { props.setOpacity(event.target.value); }; return ( { ); } -const BlendingActivator = (props:any) => { - const handleCheckboxChange = (event:any) => { - props.setBlendingActivation(event.target.checked); +const BlendingActivator = (props: any) => { + const handleCheckboxChange = (event: any) => { + props.setBlendingActivation(event.target.checked); } return ( - + /> ); } -const BlendingControl = (props:any) => { +const BlendingControl = (props: any) => { const blendingModes = [ 'difference', 'exclusion', 'color-burn', 'normal', 'multiply', 'screen', 'overlay', 'darken', 'lighten', 'color-dodge', 'hard-light', 'soft-light', 'hue', 'saturation', 'color', 'luminosity' ]; - const handleBlendingModeChange = (event:any) => { + const handleBlendingModeChange = (event: any) => { const mode = event.target.value; props.setBlendingMode(mode); if (mode !== 'normal') props.setBlendingActivation(true) @@ -237,7 +237,7 @@ const BlendingControl = (props:any) => { ); }; - + // ----------------------------------------------------- // Component: ControlPanelDrawer @@ -250,10 +250,10 @@ function ControlPanelDrawer(props: any) { }; return ( - - + /> ); @@ -323,10 +323,7 @@ function ControlPanelDrawer(props: any) { // ----------------------------------------------------- // Component: ControlPanel // ------------------------------------------------------ -function ControlPanel(props:any) { - useEffect(() => { - console.log('yo rightSelectedTms changed', props.rightSelectedTms) - }, [props.rightSelectedTms]) +function ControlPanel(props: any) { // --------------------------- // Slider control // For slider play/pause loops @@ -355,24 +352,24 @@ function ControlPanel(props:any) { const monthsCountRight = differenceInMonths(validRightMaxDate, validRightMinDate); const [leftMarks, setLeftMarks] = useState(getSliderMarksEveryYear(validLeftMinDate, validLeftMaxDate)); const [rightMarks, setRightMarks] = useState(getSliderMarksEveryYear(validRightMinDate, validRightMaxDate)); - - const validMinDate = props.clickedMap == "left" ? validLeftMinDate : validRightMinDate - const validMaxDate = props.clickedMap == "left" ? validLeftMaxDate : validRightMaxDate - const marks = props.clickedMap == "left" ? leftMarks : rightMarks - const monthsCount = props.clickedMap == "left" ? monthsCountLeft : monthsCountRight - const setMinDate = props.clickedMap == "left" ? setLeftMinDate : setRightMinDate - const setMaxDate = props.clickedMap == "left" ? setLeftMaxDate : setRightMaxDate - + + const validMinDate = props.clickedMap == "left" ? validLeftMinDate : validRightMinDate + const validMaxDate = props.clickedMap == "left" ? validLeftMaxDate : validRightMaxDate + const marks = props.clickedMap == "left" ? leftMarks : rightMarks + const monthsCount = props.clickedMap == "left" ? monthsCountLeft : monthsCountRight + const setMinDate = props.clickedMap == "left" ? setLeftMinDate : setRightMinDate + const setMaxDate = props.clickedMap == "left" ? setLeftMaxDate : setRightMaxDate + const collectionDateRetrievable: BasemapsIds[] = [BasemapsIds.Bing, BasemapsIds.PlanetMonthly] async function getCollectionDateViewport(selectedTms: BasemapsIds) { setCollectionDateStr('') - let collectionDate = {validMinDate: '?', maxDate: '?'}; + let collectionDate = { validMinDate: '?', maxDate: '?' }; const map = props.mapRef?.current?.getMap() as any; switch (+selectedTms) { - case BasemapsIds.Bing: - collectionDate = await getBingViewportDate(map) - break; + case BasemapsIds.Bing: + collectionDate = await getBingViewportDate(map) + break; default: console.log(`Cannot retrieve collection date for ${selectedTms}.`); @@ -407,15 +404,15 @@ function ControlPanel(props:any) { ); const map = props.mapRef?.current?.getMap() as any; - + // const onMoveEnd_esriWaybackMarks = useCallback((props_: any) => (e: any) => { // const onMoveEnd_esriWaybackMarks = (clickedMap: any) => (e: any) => { // console.log('1. onMoveEnd_esriWaybackMarks clickedMap & props left/right selectedTms', clickedMap) // , props.leftSelectedTms, - const onMoveEnd_esriWaybackMarks = useCallback( (e: any) => { + const onMoveEnd_esriWaybackMarks = useCallback((e: any) => { console.log('1. onMoveEnd_esriWaybackMarks clickedMapRef.current', clickedMapRef.current) // , props.leftSelectedTms, props.rightSelectedTms) // const onMoveEnd_esriWaybackMarks = (e) => { // event type: boxzoomstart - + // const esriOnMoveEnd = // ESRI Wayback Machine // const map = leftMapRef.current?.getMap() @@ -430,12 +427,13 @@ function ControlPanel(props:any) { (waybackItemsWithLocalChanges: any) => { // console.log('2. onMoveEnd_esriWaybackMarks props left/right selectedTms', clickedMap, props.leftSelectedTms, props.rightSelectedTms) // setEsriWaybackItemsChange(waybackItemsWithLocalChanges) + props.setWaybackItemsWithLocalChanges(waybackItemsWithLocalChanges) const parsedItemsWithLocalChanges = Object.values(waybackItemsWithLocalChanges).map((item: any) => { - const {itemURL, releaseDateLabel, releaseDatetime, releaseNum } = item + const { itemURL, releaseDateLabel, releaseDatetime, releaseNum } = item return { - itemURL: itemURL.replace('{level}', '{z}').replace('{row}', '{y}').replace('{column}', '{y}'), - releaseDatetime: new Date(releaseDatetime), - releaseDateLabel, + itemURL: itemURL.replace('{level}', '{z}').replace('{row}', '{y}').replace('{column}', '{y}'), + releaseDatetime: new Date(releaseDatetime), + releaseDateLabel, releaseNum } }) @@ -450,29 +448,29 @@ function ControlPanel(props:any) { // It should happen that this callback is remembered, and was setup when clickedMap was set to left or right // It is not updated via state/props if (clickedMapRef.current == 'left') { - // if (clickedMap == 'left') { - // if (+props_.leftSelectedTms == +BasemapsIds.ESRIWayback) { + // if (clickedMap == 'left') { + // if (+props_.leftSelectedTms == +BasemapsIds.ESRIWayback) { console.log('leftSelectedTms == ESRIWayback') setLeftMinDate(waybackMinDate) setLeftMaxDate(waybackMaxDate) - setLeftMarks(esriWaybackMarks) - } + setLeftMarks(esriWaybackMarks) + } if (clickedMapRef.current == 'right') { - // if (clickedMap == 'right') { - // if (+props_.rightSelectedTms == +BasemapsIds.ESRIWayback) { + // if (clickedMap == 'right') { + // if (+props_.rightSelectedTms == +BasemapsIds.ESRIWayback) { // TODO props_.rightSelectedTms won't get changed since useCallback when changing selectedTms afterwards console.log('rightSelectedTms == ESRIWayback') setRightMinDate(waybackMinDate) setRightMaxDate(waybackMaxDate) - setRightMarks(esriWaybackMarks) - } + setRightMarks(esriWaybackMarks) + } } ); } - , []) + , []) const clickedMapRef = useRef(props.clickedMap) - useEffect( + useEffect( () => { const map = props.mapRef.current?.getMap() clickedMapRef.current = props.clickedMap @@ -483,13 +481,13 @@ function ControlPanel(props:any) { setMinDate(validMinDate <= MIN_PLANET_DATE ? MIN_PLANET_DATE : validMinDate) setMaxDate(validMaxDate >= MAX_PLANET_DATE ? MAX_PLANET_DATE : validMaxDate) const planetMarks = getSliderMarksEveryYear(validMinDate, validMaxDate) - props.clickedMap == "left" ? setLeftMarks(planetMarks) : setRightMarks(planetMarks) + props.clickedMap == "left" ? setLeftMarks(planetMarks) : setRightMarks(planetMarks) } else if (props.selectedTms == BasemapsIds.ESRIWayback) { - map.on('moveend', onMoveEnd_esriWaybackMarks); - onMoveEnd_esriWaybackMarks({target: map}) + map.on('moveend', onMoveEnd_esriWaybackMarks); + onMoveEnd_esriWaybackMarks({ target: map }) } - }, + }, [props.clickedMap, props.selectedTms, props.mapRef] ) @@ -508,17 +506,17 @@ function ControlPanel(props:any) { // Note html2canvas cannot export with mixBlendModes and clipPath yet, see https://github.com/niklasvh/html2canvas/issues/580 if (exportFramesMode == ExportButtonOptions.COMPOSITED) { toPng(document.getElementById('mapsParent') || document.body) - .then(function (dataUrl) { - const a = document.createElement('a') - a.setAttribute('download', 'composited.png') - a.setAttribute('href', dataUrl) - a.click() - }) - .catch(function (error) { - console.error('Error with downloading of composited image!', error); - }); + .then(function (dataUrl) { + const a = document.createElement('a') + a.setAttribute('download', 'composited.png') + a.setAttribute('href', dataUrl) + a.click() + }) + .catch(function (error) { + console.error('Error with downloading of composited image!', error); + }); } - + else { const aDiv = document.getElementById( "downloadFramesDiv" @@ -537,13 +535,13 @@ function ControlPanel(props:any) { // ) // : [false]; const filteredPlanetDates = - eachMonthOfInterval({ - start: validMinDate, - end: validMaxDate, - }).filter((_: Date, i: number) => i % exportInterval == 0) + eachMonthOfInterval({ + start: validMinDate, + end: validMaxDate, + }).filter((_: Date, i: number) => i % exportInterval == 0) - - function get_batch_cmd (tmsUrl: string, bounds, filename: string,) { + + function get_batch_cmd(tmsUrl: string, bounds, filename: string,) { const downloadUrl = titilerCropUrl( bounds, tmsUrl, @@ -551,13 +549,12 @@ function ControlPanel(props:any) { titilerEndpoint ); const batch_cmd = `REM ${filename}\nREM ${downloadUrl}\n` + - // gdal_translate command - `%QGIS%\\bin\\gdal_translate -projwin ${bounds.getWest()} ${bounds.getNorth()} ${bounds.getEast()} ${bounds.getSouth()} -projwin_srs EPSG:4326 -outsize %BASEMAP_WIDTH% 0 "${buildGdalWmsXml(tmsUrl)}" %DOWNLOAD_FOLDER%\\${ - filename + "_gdal.tif" - }`; + // gdal_translate command + `%QGIS%\\bin\\gdal_translate -projwin ${bounds.getWest()} ${bounds.getNorth()} ${bounds.getEast()} ${bounds.getSouth()} -projwin_srs EPSG:4326 -outsize %BASEMAP_WIDTH% 0 "${buildGdalWmsXml(tmsUrl)}" %DOWNLOAD_FOLDER%\\${filename + "_gdal.tif" + }`; return { downloadUrl, batch_cmd, filename } } - + const gdalTranslateCmds_planet = filteredPlanetDates.map((date) => { const tmsUrl = planetBasemapUrl(date); const date_YYYY_MM = formatDate(date); @@ -579,9 +576,9 @@ function ControlPanel(props:any) { const tmsUrl = basemapsTmsSources[key].url const cmd_obj = get_batch_cmd(tmsUrl, bounds, filename) return cmd_obj; - }) + }) - const gdalTranslateCmds = [...gdalTranslateCmds_other, ...gdalTranslateCmds_planet ] + const gdalTranslateCmds = [...gdalTranslateCmds_other, ...gdalTranslateCmds_planet] // const gdalTranslateCmds = [...gdalTranslateCmds_planet ] // Write gdal_translate command to batch script with indices to original location of cropped version @@ -631,7 +628,7 @@ function ControlPanel(props:any) { console.log('exportFramesMode == ExportButtonOptions.ALL_FRAMES') fetchTitilerFramesBatches(gdalTranslateCmds, aDiv); } - } + } } // console.log(props.timelineDate, 'timelineDate') return ( @@ -664,28 +661,28 @@ function ControlPanel(props:any) { height: "auto", }} > -
- } useFlexGap flexWrap="wrap" > - - + {/* {props.splitScreenMode === "split-screen" && ( */} - {/* )} */} - - +
{ - +props.selectedTms !== BasemapsIds.PlanetMonthly && + +props.selectedTms !== BasemapsIds.PlanetMonthly && ( -
- {( collectionDateActivated && props.selectedTms == BasemapsIds.Bing ) ? ( - // {(( collectionDateActivated && collectionDateRetrievable.includes((props.selectedTms as BasemapsIds)) )) && ( + {(collectionDateActivated && props.selectedTms == BasemapsIds.Bing) ? ( + // {(( collectionDateActivated && collectionDateRetrievable.includes((props.selectedTms as BasemapsIds)) )) && ( - - - ) : <>{" "}} + }}> + Collection Date: {collectionDateStr} + + + ) : <>{" "}}
) } @@ -853,7 +850,7 @@ function ControlPanel(props:any) { // min={0} max={monthsCount} - marks={ marks } + marks={marks} // value={dateToSliderVal(props.timelineDate, validMinDate)} onChange={handleSliderChange} diff --git a/src/utilities.tsx b/src/utilities.tsx index 102fe61..7da6f34 100644 --- a/src/utilities.tsx +++ b/src/utilities.tsx @@ -17,7 +17,7 @@ const tileMatrixSetId = 'WebMercatorQuad' // EPSG: 3857 https://titiler.xyz/tile const yandexGdalWmsXml = 'https://core-sat.maps.yandex.net/tiles?l=sat&x=${x}&y=${y}&z=${z}&scale=1&lang=ru_RU-20037508.3420037508.3420037508.34-20037508.342011topEPSG:33952562563' const yandexGdalUrl = encodeURIComponent(yandexGdalWmsXml) // const yandex_url = "https://core-sat.maps.yandex.net/tiles?l=sat&x={x}&y={y}&z={z}&scale=1&lang=ru_RU" -const yandex_url = `${TITILER_ENDPOINT}/cog/tiles/${tileMatrixSetId}/{z}/{x}/{y}?url=${yandexGdalUrl}`; +const yandex_url = `${TITILER_ENDPOINT}/cog/tiles/${tileMatrixSetId}/{z}/{x}/{y}?url=${yandexGdalUrl}`; // Helper functions to convert between date for date-picker and slider-value @@ -38,7 +38,7 @@ export const MIN_PLANET_DATE = new Date("2016-01-01T00:00:00.000"); export const MAX_PLANET_DATE = subMonths(new Date(), 1); -export function useWaybackUrl(date: Date) { +export function useWaybackUrl(date: Date, waybackItemsWithLocalChanges: Array) { const [wayBackItems, setWaybackItems] = useState([]); const [loading, setLoading] = useState(true); const [url, setUrl] = useState(String); @@ -53,21 +53,28 @@ export function useWaybackUrl(date: Date) { }, []); useEffect(() => { - + // itemURL: itemURL.replace('{level}', '{z}').replace('{row}', '{y}').replace('{column}', '{y}'), // releaseDatetime: new Date(releaseDatetime), // releaseDateLabel, // releaseNum - const sortedItems = Object.values(wayBackItems).sort( function (a, b) { + let waybackItems_: Array; + if (waybackItemsWithLocalChanges && waybackItemsWithLocalChanges.length > 0) { + waybackItems_ = waybackItemsWithLocalChanges + } else { + waybackItems_ = wayBackItems + } + + const sortedItems = Object.values(waybackItems_).sort(function (a, b) { return a.releaseDatetime - b.releaseDatetime; - } ) + }) const closestSuperior = sortedItems.find( - item => (new Date(item.releaseDatetime) > date), + item => (new Date(item.releaseDatetime) > date), sortedItems ) // console.log('ESRI Wayback testing ', sortedItems, closestSuperior, closestSuperior?.itemURL) - if (closestSuperior){ + if (closestSuperior) { const closestUrl = closestSuperior?.itemURL .replace('{level}', '{z}') .replace('{row}', '{y}') @@ -83,7 +90,7 @@ export function useWaybackUrl(date: Date) { -const planetBasemapUrl = (date: Date, customApi?:string) => { +const planetBasemapUrl = (date: Date, customApi?: string) => { // basemap_date_str = "2019_01"; return `https://tiles.planet.com/basemaps/v1/planet-tiles/global_monthly_${format( date, @@ -157,7 +164,7 @@ const basemapsTmsSources: any = { maxzoom: 20, }, [BasemapsIds.Apple]: { - url: "https://sat-cdn3.apple-mapkit.com/tile?style=7&size=1&scale=1&z={z}&x={x}&y={y}&v=9801&accessKey=1721982180_2825845599881494057_%2F_UvNg5jEboEb8eMslp86Eeymjt%2FfRcTunBvgsiAiEb6Q%3D", + url: "https://sat-cdn3.apple-mapkit.com/tile?style=7&size=1&scale=1&z={z}&x={x}&y={y}&v=9801&accessKey=1721982180_2825845599881494057_%2F_UvNg5jEboEb8eMslp86Eeymjt%2FfRcTunBvgsiAiEb6Q%3D", maxzoom: 20, }, [BasemapsIds.Yandex]: { @@ -292,134 +299,134 @@ function objectsHaveSameKeys(...objects: any): boolean { } - // For bing Aerial, can retrieve the imagery tile collection date min/max properties - and aggregate into a viewport min/max - // Could also be displayed on top of each tile, in a corner, so no aggregation needed. - // type mapboxgl.TransformRequestFunction = (url: string, resourceType: mapboxgl.ResourceType) => mapboxgl.RequestParameters - let numTilesLoaded = 0; - const tiles_dates = [] - const transformRequest = function ( - url: string, - resourceType: mapboxgl.ResourceType - ) { - if (numTilesLoaded === undefined) numTilesLoaded = 0; - if (resourceType == "Tile") { - numTilesLoaded++; - console.log( - "numTilesLoaded from beginning/component load", - numTilesLoaded - ); +// For bing Aerial, can retrieve the imagery tile collection date min/max properties - and aggregate into a viewport min/max +// Could also be displayed on top of each tile, in a corner, so no aggregation needed. +// type mapboxgl.TransformRequestFunction = (url: string, resourceType: mapboxgl.ResourceType) => mapboxgl.RequestParameters +let numTilesLoaded = 0; +const tiles_dates = [] +const transformRequest = function ( + url: string, + resourceType: mapboxgl.ResourceType +) { + if (numTilesLoaded === undefined) numTilesLoaded = 0; + if (resourceType == "Tile") { + numTilesLoaded++; + console.log( + "numTilesLoaded from beginning/component load", + numTilesLoaded + ); - if (url.includes("virtualearth.net")) getBingDatesFromUrl(url); - } - return { url }; - } as mapboxgl.TransformRequestFunction; - - function getBingDatesFromResponse(response: Response) { - const dates_str = response.headers - .get("X-Ve-Tilemeta-Capturedatesrange") - ?.split("-"); - const dates = dates_str?.map((s) => new Date(s)); - return dates; + if (url.includes("virtualearth.net")) getBingDatesFromUrl(url); } - function getVisibleTilesXYZ(map: mapboxgl.Map, tileSize: number) { - const tiles = []; - const zoom = Math.floor(map.getZoom()) + 1; - const bounds = map.getBounds(); - // const topLeft = map.project(bounds.getNorthWest()); - // const bottomRight = map.project(bounds.getSouthEast()); - const topLeft = lngLatToWorld(bounds.getNorthWest().toArray()).map( - (x) => (x / 512) * 2 ** zoom - ); - const bottomRight = lngLatToWorld(bounds.getSouthEast().toArray()).map( - (x) => (x / 512) * 2 ** zoom - ); - console.log("getVisibleTilesXYZ", map, zoom, bounds, topLeft, bottomRight); + return { url }; +} as mapboxgl.TransformRequestFunction; + +function getBingDatesFromResponse(response: Response) { + const dates_str = response.headers + .get("X-Ve-Tilemeta-Capturedatesrange") + ?.split("-"); + const dates = dates_str?.map((s) => new Date(s)); + return dates; +} +function getVisibleTilesXYZ(map: mapboxgl.Map, tileSize: number) { + const tiles = []; + const zoom = Math.floor(map.getZoom()) + 1; + const bounds = map.getBounds(); + // const topLeft = map.project(bounds.getNorthWest()); + // const bottomRight = map.project(bounds.getSouthEast()); + const topLeft = lngLatToWorld(bounds.getNorthWest().toArray()).map( + (x) => (x / 512) * 2 ** zoom + ); + const bottomRight = lngLatToWorld(bounds.getSouthEast().toArray()).map( + (x) => (x / 512) * 2 ** zoom + ); + console.log("getVisibleTilesXYZ", map, zoom, bounds, topLeft, bottomRight); + for ( + let x = Math.floor(topLeft[0]); // .x + x <= Math.floor(bottomRight[0]); + x++ + ) { for ( - let x = Math.floor(topLeft[0]); // .x - x <= Math.floor(bottomRight[0]); - x++ + let y = Math.floor(topLeft[1]); // .y + y >= Math.floor(bottomRight[1]); + y-- ) { - for ( - let y = Math.floor(topLeft[1]); // .y - y >= Math.floor(bottomRight[1]); - y-- - ) { - tiles.push({ x, y, z: zoom }); - } + tiles.push({ x, y, z: zoom }); } - - return tiles; } - function toQuad(x: number, y: number, z: number) { - var quadkey = ""; - for (var i = z; i >= 0; --i) { - var bitmask = 1 << i; - var digit = 0; - if ((x & bitmask) !== 0) { - digit |= 1; - } - if ((y & bitmask) !== 0) { - digit |= 2; - } - quadkey += digit; + + return tiles; +} +function toQuad(x: number, y: number, z: number) { + var quadkey = ""; + for (var i = z; i >= 0; --i) { + var bitmask = 1 << i; + var digit = 0; + if ((x & bitmask) !== 0) { + digit |= 1; } - return quadkey; - } - function getBingUrl(quadkey: string) { - // return "https://t.ssl.ak.tiles.virtualearth.net/tiles/a12022010003311020210.jpeg?g=13578&n=z&prx=1"; - return basemapsTmsSources[BasemapsIds.Bing].url.replace( - "{quadkey}", - quadkey - ); - } - async function getBingDatesFromUrl(url: string) { - const dates = await fetch(url).then(function (response) { - // In the bing case, can look for a response header property - console.log(url, response.headers); - const dates = getBingDatesFromResponse(response); - console.log("getBingDatesFromUrl, in fetch", dates); - return dates; - }); - return dates ?? "error on fetch ?"; + if ((y & bitmask) !== 0) { + digit |= 2; + } + quadkey += digit; } + return quadkey; +} +function getBingUrl(quadkey: string) { + // return "https://t.ssl.ak.tiles.virtualearth.net/tiles/a12022010003311020210.jpeg?g=13578&n=z&prx=1"; + return basemapsTmsSources[BasemapsIds.Bing].url.replace( + "{quadkey}", + quadkey + ); +} +async function getBingDatesFromUrl(url: string) { + const dates = await fetch(url).then(function (response) { + // In the bing case, can look for a response header property + console.log(url, response.headers); + const dates = getBingDatesFromResponse(response); + console.log("getBingDatesFromUrl, in fetch", dates); + return dates; + }); + return dates ?? "error on fetch ?"; +} - async function getBingViewportDate(map: any) { - const urlArray = getVisibleTilesXYZ(map, 256); // source.tileSize) - console.log(urlArray); - const quadkeysArray = urlArray.map((xyz: any) => toQuad(xyz.x, xyz.y, xyz.z)); - console.log(quadkeysArray); - const bingUrls = quadkeysArray.map((quadkey: string) => getBingUrl(quadkey)); - console.log(bingUrls); - - const promArray = bingUrls.map(async (url) => { - return await getBingDatesFromUrl(url); - }); - // console.log("promArray", promArray); - // Promise.all(promArray).then((dates) => { - // console.log("after promise.all", dates); - // const minDate = Math.min(...(dates as any).map((d: number[]) => d[0])); - // const maxDate = Math.max(...(dates as any).map((d: number[]) => d[1])); - // console.log(dates, minDate, maxDate); - // document.a = dates; - // }); - - const tilesDates = await Promise.all( - bingUrls.map(async (url) => await getBingDatesFromUrl(url)) - ); - const minDate = new Date( - Math.min(...(tilesDates as any).map((d: number[]) => d[0])) - ) - .toISOString() - .slice(0, 10); - const maxDate = new Date( - Math.max(...(tilesDates as any).map((d: number[]) => d[1])) - ) - .toISOString() - .slice(0, 10); - console.log("yaya", tilesDates, "\n", minDate, maxDate); - return {minDate, maxDate} - } +async function getBingViewportDate(map: any) { + const urlArray = getVisibleTilesXYZ(map, 256); // source.tileSize) + console.log(urlArray); + const quadkeysArray = urlArray.map((xyz: any) => toQuad(xyz.x, xyz.y, xyz.z)); + console.log(quadkeysArray); + const bingUrls = quadkeysArray.map((quadkey: string) => getBingUrl(quadkey)); + console.log(bingUrls); + + const promArray = bingUrls.map(async (url) => { + return await getBingDatesFromUrl(url); + }); + // console.log("promArray", promArray); + // Promise.all(promArray).then((dates) => { + // console.log("after promise.all", dates); + // const minDate = Math.min(...(dates as any).map((d: number[]) => d[0])); + // const maxDate = Math.max(...(dates as any).map((d: number[]) => d[1])); + // console.log(dates, minDate, maxDate); + // document.a = dates; + // }); + + const tilesDates = await Promise.all( + bingUrls.map(async (url) => await getBingDatesFromUrl(url)) + ); + const minDate = new Date( + Math.min(...(tilesDates as any).map((d: number[]) => d[0])) + ) + .toISOString() + .slice(0, 10); + const maxDate = new Date( + Math.max(...(tilesDates as any).map((d: number[]) => d[1])) + ) + .toISOString() + .slice(0, 10); + console.log("yaya", tilesDates, "\n", minDate, maxDate); + return { minDate, maxDate } +} export {