-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Split left map controls to its own component file
For easier adding of FileInput
- Loading branch information
Showing
3 changed files
with
228 additions
and
19 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
// Forked from CustomOverlay https://github.com/visgl/react-map-gl/tree/7.0-release/examples/custom-overlay | ||
// Minimal example here: https://visgl.github.io/react-map-gl/docs/get-started/adding-custom-data | ||
|
||
import * as React from 'react' | ||
import { useState, cloneElement } from 'react' | ||
import { useControl } from 'react-map-gl' | ||
import { createPortal } from 'react-dom' | ||
import PropTypes from 'prop-types' | ||
|
||
import type { MapboxMap, IControl } from 'react-map-gl' | ||
|
||
class OverlayControl implements IControl { | ||
_map: MapboxMap | null = null | ||
_container: HTMLElement | ||
_redraw: () => void | ||
|
||
constructor(redraw: () => void) { | ||
this._redraw = redraw | ||
} | ||
|
||
onAdd(map): HTMLElement { | ||
this._map = map | ||
map.on('move', this._redraw) | ||
/* global document */ | ||
this._container = document.createElement('div') | ||
this._redraw() | ||
return this._container | ||
} | ||
|
||
onRemove(): void { | ||
this._container.remove() | ||
this._map?.off('move', this._redraw) | ||
this._map = null | ||
} | ||
|
||
getMap(): MapboxMap | null { | ||
return this._map | ||
} | ||
|
||
getElement(): HTMLElement { | ||
return this._container | ||
} | ||
} | ||
|
||
/** | ||
* A custom control that rerenders arbitrary React content whenever the camera changes | ||
*/ | ||
CustomOverlay.propTypes = { | ||
position: PropTypes.string, | ||
// children: React.ReactElement, | ||
} | ||
function CustomOverlay(props): React.ReactElement { | ||
const [, setVersion] = useState(0) | ||
|
||
const ctrl = useControl<OverlayControl>( | ||
() => { | ||
const forceUpdate = (): void => { | ||
setVersion((v) => v + 1) | ||
} | ||
return new OverlayControl(forceUpdate) | ||
}, | ||
{ position: props.position } | ||
) | ||
|
||
const map = ctrl.getMap() | ||
return map && createPortal(cloneElement(props.children, { map }), ctrl.getElement()) | ||
} | ||
|
||
export default React.memo(CustomOverlay) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
import {ReactElement, memo, useCallback} from 'react' | ||
import CustomOverlay from './custom-overlay'; | ||
import { ScaleControl, NavigationControl, useControl } from "react-map-gl"; | ||
import GeocoderControl from './geocoder-control' | ||
import RulerControl from '@mapbox-controls/ruler'; | ||
|
||
import bbox from '@turf/bbox' | ||
import { kml } from '@tmcw/togeojson' | ||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' | ||
import { faUpload } from '@fortawesome/free-solid-svg-icons' | ||
|
||
|
||
export function WrappedRulerControl(props: any) { | ||
useControl(() => new RulerControl(props), { | ||
position: props.position | ||
}); | ||
return null; | ||
} | ||
|
||
function FileInput(props: any): ReactElement { | ||
function handleKMLUpload(event): void { | ||
event.preventDefault() | ||
const kmlFile = event.target.files[0] | ||
console.log('kmlFile info', kmlFile) | ||
console.log('props in handleKMLUpload', props) | ||
const reader = new FileReader() | ||
reader.onload = async (e) => { | ||
const kmlContent: string = e.target?.result as string | ||
const xmlDoc = new DOMParser().parseFromString(kmlContent, 'text/xml') | ||
const geojsonFeatures = kml(xmlDoc) | ||
console.log('geojsonFeatures from kml', geojsonFeatures) | ||
|
||
// Zoom on imported kml | ||
const bounds = bbox(geojsonFeatures) | ||
const [minLng, minLat, maxLng, maxLat] = bounds | ||
console.log('mapRef', props) | ||
props.mapRef.current.fitBounds( | ||
[ | ||
[minLng, minLat], | ||
[maxLng, maxLat], | ||
], | ||
{ | ||
padding: 150, | ||
duration: 1000, | ||
center: [0.5 * (minLng + maxLng), 0.5 * (minLat + maxLat)], | ||
} | ||
) | ||
|
||
// Can be imported but not edited because not added to the draw features component | ||
console.log('props in onload', props) | ||
props.setDrawFeatures([{ ...geojsonFeatures.features[0], id: 'imported_kml_feature' }]) | ||
// Not useful | ||
// props.map.fire("draw.create", { | ||
// features: [{...geojsonFeatures.features[0], id: 'imported_kml_feature'}] | ||
// }); | ||
} | ||
reader.readAsText(event.target.files[0], 'UTF-8') | ||
} | ||
|
||
return ( | ||
<div className="mapboxgl-ctrl mapboxgl-ctrl-group"> | ||
<input | ||
id="kmlUploadInput" | ||
type="file" | ||
onChange={(e) => { | ||
handleKMLUpload(e) | ||
}} | ||
style={{ pointerEvents: 'auto', display: 'none' }} | ||
/> | ||
<button | ||
type="button" | ||
title="Upload KML AOI (will just zoom to it, not load it)" | ||
onClick={() => { | ||
document.getElementById('kmlUploadInput')?.click() | ||
}} | ||
> | ||
<span className="mapboxgl-ctrl-icon" style={{ padding: '7px' }}> | ||
<FontAwesomeIcon icon={faUpload} />{' '} | ||
</span> | ||
</button> | ||
</div> | ||
) | ||
} | ||
|
||
|
||
function FileUploadControl(props: any): ReactElement { | ||
const onDrawCreate = useCallback((e) => { | ||
props.setDrawFeatures((currFeatures) => { | ||
const newFeatures = { ...currFeatures } | ||
for (const f of e.features) { | ||
newFeatures[f.id] = f | ||
} | ||
return newFeatures | ||
}) | ||
}, []) | ||
const onDrawUpdate = useCallback((e) => { | ||
props.setDrawFeatures((currFeatures) => { | ||
const newFeatures = { ...currFeatures } | ||
for (const f of e.features) { | ||
newFeatures[f.id] = f | ||
} | ||
return newFeatures | ||
}) | ||
}, []) | ||
|
||
const onDrawDelete = useCallback((e) => { | ||
props.setDrawFeatures((currFeatures) => { | ||
const newFeatures = { ...currFeatures } | ||
for (const f of e.features) { | ||
delete newFeatures[f.id] | ||
} | ||
return newFeatures | ||
}) | ||
}, []) | ||
|
||
return ( | ||
|
||
<CustomOverlay position="top-left" style={{ pointerEvents: 'all' }}> | ||
<FileInput setDrawFeatures={props.setDrawFeatures} mapRef={props.mapRef} /> | ||
</CustomOverlay> | ||
) | ||
} | ||
|
||
function MapControls(props: any): ReactElement { | ||
|
||
|
||
return ( | ||
<> | ||
<GeocoderControl | ||
mapboxAccessToken={props.mapboxAccessToken} | ||
position="top-left" | ||
flyTo={false} | ||
mapRef={props.mapRef} | ||
/> | ||
<WrappedRulerControl | ||
position="top-left" | ||
/> | ||
|
||
{/* <FileUploadControl | ||
/> */} | ||
<NavigationControl showCompass={false} position="top-left" /> | ||
|
||
<ScaleControl | ||
unit={'metric'} | ||
position="top-left" | ||
// style={{ clear: 'none' }} | ||
maxWidth={60} | ||
/> | ||
</> | ||
) | ||
} | ||
|
||
export default memo(MapControls) |