Skip to content

Commit

Permalink
[upload wizard] show more accurate bbox for osmPatch files
Browse files Browse the repository at this point in the history
  • Loading branch information
k-yle committed Dec 27, 2023
1 parent 1e53397 commit f2e2f5e
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 38 deletions.
41 changes: 7 additions & 34 deletions src/pages/upload/MapPreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,48 +4,21 @@ import { FeatureGroup, MapContainer, Polygon } from 'react-leaflet';
import { type FeatureGroup as IFeatureGroup, LatLngBounds } from 'leaflet';
import { Layers } from '../map/Layers';
import type { FetchCache } from './util';
import { recurseToNodes } from './helpers/recurseToNodes';

/**
* For osmPatch files, we have access to all the ways/relation
* members from when we ran the diff. So we can use that data.
*
* But for osmChange files,
* this doesn't work for ways or relations, so it's pretty dumb.
* It only considers the nodes that exist in the osmChange file.
* See https://wiki.osm.org/API_v0.6#Bounding_box_computation
*/
function getCsBbox(osmChange: OsmChange, fetchCache: FetchCache | undefined) {
const bbox = {
minLat: Infinity,
minLng: Infinity,
maxLat: -Infinity,
maxLng: -Infinity,
};

const filteredNodes = Object.values(osmChange)
.flat()
.flatMap((x) => recurseToNodes(x, fetchCache));

for (const node of filteredNodes) {
if (node.lat < bbox.minLat) bbox.minLat = node.lat;
if (node.lon < bbox.minLng) bbox.minLng = node.lon;
if (node.lat > bbox.maxLat) bbox.maxLat = node.lat;
if (node.lon > bbox.maxLng) bbox.maxLng = node.lon;
}

return bbox;
}
import { type Bbox, getCsBbox } from './helpers/bbox';

export const MapPreview: React.FC<{
diff: OsmChange;
fetchCache: FetchCache | undefined;
}> = ({ diff, fetchCache }) => {
bboxFromOsmPatch: Bbox | undefined;
}> = ({ diff, fetchCache, bboxFromOsmPatch }) => {
const polygonGroup = useRef<IFeatureGroup>(null);

// TODO: react hook to download all nodes of ways/relations that were touched
// for small features. Render each feature on the map
const bbox = useMemo(() => getCsBbox(diff, fetchCache), [diff, fetchCache]);
const bbox = useMemo(
() => getCsBbox(diff, fetchCache, bboxFromOsmPatch),
[diff, fetchCache, bboxFromOsmPatch],
);

if (Object.values(bbox).some((n) => !Number.isFinite(n))) {
return <>No preview available</>;
Expand Down
12 changes: 10 additions & 2 deletions src/pages/upload/Upload.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { createOsmChangeFromPatchFile } from './createOsmChangeFromPatchFile';
import './Upload.css';
import { type FetchCache, downloadFile } from './util';
import { RelationMemberChanges } from './RelationMemberChanges';
import type { Bbox } from './helpers/bbox';

const DEFAULT_TAGS = {
attribution: 'https://wiki.openstreetmap.org/wiki/Contributors#LINZ',
Expand Down Expand Up @@ -57,6 +58,7 @@ const UploadInner: React.FC = () => {
const [fetchCache, setFetchCache] = useState<FetchCache>();
const [fileName, setFileName] = useState<string>();
const [messages, setMessages] = useState<string[]>([]);
const [bboxFromOsmPatch, setBboxFromOsmPatch] = useState<Bbox>();

const input = useRef<HTMLInputElement>(null);

Expand Down Expand Up @@ -129,8 +131,10 @@ const UploadInner: React.FC = () => {

setMessages([...instructions]);

const { osmChange, fetched } = await createOsmChangeFromPatchFile(merged);
const { osmChange, fetched, bbox } =
await createOsmChangeFromPatchFile(merged);
setFetchCache(fetched);
setBboxFromOsmPatch(bbox);
return setDiff(osmChange);
} catch (ex) {
console.error(ex);
Expand Down Expand Up @@ -232,7 +236,11 @@ const UploadInner: React.FC = () => {
<PlusMinus diff={diff} />
<strong>Approximate Extent of nodes:</strong>
<br />
<MapPreview diff={diff} fetchCache={fetchCache} />
<MapPreview
diff={diff}
fetchCache={fetchCache}
bboxFromOsmPatch={bboxFromOsmPatch}
/>
<br />
<br />
<strong>Tag Changes:</strong>
Expand Down
7 changes: 5 additions & 2 deletions src/pages/upload/createOsmChangeFromPatchFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import type {
import { MAP, type NWR } from '../HistoryRestorer/util';
import type { OsmPatch } from '../../types';
import { type FetchCache, fetchChunked } from './util';
import { type Bbox, getGeoJsonBbox } from './helpers/bbox';

window.structuredClone ||= (x) => JSON.parse(JSON.stringify(x));

Expand Down Expand Up @@ -227,7 +228,7 @@ function updateRelationMembers(

export async function createOsmChangeFromPatchFile(
osmPatch: OsmPatch,
): Promise<{ osmChange: OsmChange; fetched: FetchCache }> {
): Promise<{ osmChange: OsmChange; fetched: FetchCache; bbox: Bbox }> {
const osmChange: OsmChange = {
create: [],
delete: [],
Expand Down Expand Up @@ -297,5 +298,7 @@ export async function createOsmChangeFromPatchFile(
}
}

return { osmChange, fetched };
const bbox = getGeoJsonBbox(osmPatch);

return { osmChange, fetched, bbox };
}
75 changes: 75 additions & 0 deletions src/pages/upload/helpers/bbox.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import type { OsmChange } from 'osm-api';
import type { FetchCache } from '../util';
import type { OsmPatch } from '../../../types';
import { recurseToNodes } from './recurseToNodes';

export const createEmptyBbox = () => ({
minLat: Infinity,
minLng: Infinity,
maxLat: -Infinity,
maxLng: -Infinity,
});

export type Bbox = ReturnType<typeof createEmptyBbox>;

/**
* This will try to get the geometry of all ways/relations,
* but in most cases, these nodes won't be in the fetchCache.
*
* For osmPatch files only, we can also use the geometry
* from the geojson file.
*/
export function getCsBbox(
osmChange: OsmChange,
fetchCache: FetchCache | undefined,
initialBbox: Bbox | undefined,
): Bbox {
const bbox = initialBbox || createEmptyBbox();

const filteredNodes = Object.values(osmChange)
.flat()
.flatMap((x) => recurseToNodes(x, fetchCache));

for (const node of filteredNodes) {
if (node.lat < bbox.minLat) bbox.minLat = node.lat;
if (node.lon < bbox.minLng) bbox.minLng = node.lon;
if (node.lat > bbox.maxLat) bbox.maxLat = node.lat;
if (node.lon > bbox.maxLng) bbox.maxLng = node.lon;
}

return bbox;
}

/**
* Gets the bbox based on the geometry defined in an
* osmPatch file, which may or may not match the osm
* geometry (for edit and delete actions).
*/
export function getGeoJsonBbox(osmPatch: OsmPatch): Bbox {
const allCoords: number[] = [];
for (const f of osmPatch.features) {
if ('coordinates' in f.geometry) {
const flatCoords = f.geometry.coordinates.flat(3);
allCoords.push(...flatCoords);
}
}

const bbox = createEmptyBbox();
for (let index = 0; index < allCoords.length; index++) {
// we can safely use odd vs even, no matter how deep
// the array of coordinates was.
if (index % 2) {
// this is a lat
const lat = allCoords[index];
if (lat < bbox.minLat) bbox.minLat = lat;
if (lat > bbox.maxLat) bbox.maxLat = lat;
} else {
// this is a lng
const lon = allCoords[index];
if (lon < bbox.minLng) bbox.minLng = lon;
if (lon > bbox.maxLng) bbox.maxLng = lon;
}
}

return bbox;
}

0 comments on commit f2e2f5e

Please sign in to comment.