Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support importing osmChange and geojson files #10407

Draft
wants to merge 1 commit into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion config/eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ export default [
'indent': ['off', 4],
'keyword-spacing': 'error',
'linebreak-style': ['error', 'unix'],
'no-await-in-loop': 'error',
'no-caller': 'error',
'no-catch-shadow': 'error',
'no-console': 'warn',
Expand Down
10 changes: 10 additions & 0 deletions css/80_app.css
Original file line number Diff line number Diff line change
Expand Up @@ -135,19 +135,29 @@ em {
strong {
font-weight: bold;
}

.button-link,
a,
a:visited,
a:active {
color: #7092ff;
}
.button-link:focus,
a:focus {
color: #597be7;
}
@media (hover: hover) {
.button-link:hover,
a:hover {
color: #597be7;
}
}

.button-link {
display: flex;
align-items: center;
}

kbd {
display: inline-block;
text-align: center;
Expand Down
11 changes: 11 additions & 0 deletions data/core.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,17 @@ en:
area: This feature can't follow the area because it is only connected at a single point. Add another point manually to continue.
generic: This feature can't follow the other feature because they are only connected at a single point. Add another point manually to continue.
unknown: This feature can't follow another feature.
import_from_file:
title: Load edits from file
tooltip: Import an osmChange file or osmPatch file into the editor.
loading: Importing file…
error:
title: Failed to import file
unknown: An unknown error occurred.
conflicts: Some features in the osmChange file have been edited by other users, causing a conflict.
annotation:
osmChange: Imported an osmChange file
osmPatch: Imported an osmPatch file
reflect:
title:
long: Flip Long
Expand Down
6 changes: 6 additions & 0 deletions jsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"compilerOptions": {
"downlevelIteration": true,
"types": ["modules/global.d.ts"]
}
}
159 changes: 159 additions & 0 deletions modules/actions/import_osmChange.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
// @ts-check
/** @import * as Osm from 'osm-api' */
import { osmNode, osmRelation, osmWay } from '../osm';
import { actionAddEntity } from './add_entity';
import { actionChangeTags } from './change_tags';
import { actionDeleteNode } from './delete_node';
import { actionDeleteRelation } from './delete_relation';
import { actionDeleteWay } from './delete_way';
import { actionMoveNode } from './move_node';
import { actionReplaceRelationMembers } from './replace_relation_members';
import { actionReplaceWayNodes } from './replace_way_nodes';

/**
* A map of the IDs from the osmPatch file to the IDs in our graph.
* @typedef {Record<Osm.OsmFeatureType, Record<number, string>>} IDMap
*/

/**
* @param {Osm.OsmFeatureType} type
* @param {number} id
* @param {IDMap} idMap
*/
const getId = (type, id, idMap) => {
const mappedId = id > 0 ? type[0] + id : idMap[type][id];
if (mappedId === undefined) {
throw new Error(`No entry in idMap for ${type} ${id}`);
}
return mappedId;
};

/**
* @param {Osm.OsmChange} osmChange
* @param {boolean} allowConflicts
*/
export function actionImportOsmChange(osmChange, allowConflicts) {
/** @param {iD.Graph} graph */
return (graph) => {
/** @type {IDMap} */
const idMap = { node: {}, way: {}, relation: {} };

// check that the versions from the osmChange file match the
// versions in our graph. If not, there are conflicts.
if (!allowConflicts) {
for (const feature of [...osmChange.modify, ...osmChange.delete]) {
const entityId = getId(feature.type, feature.id, idMap);
const entity = graph.entity(entityId);
if (+entity.version !== feature.version) {
throw new Error(
`Conflicts on ${entityId}, expected v${feature.version}, got v${entity.version}`
);
}
}
}

// create placeholders in the graph for all new features, so
// that all new features are allocated an ID.
for (const feature of osmChange.create) {
switch (feature.type) {
case 'node': {
const entity = osmNode({
tags: feature.tags,
loc: [feature.lon, feature.lat],
});
idMap[feature.type][feature.id] = entity.id;
graph = actionAddEntity(entity)(graph);
break;
}

case 'way': {
const entity = osmWay({
tags: feature.tags,
// `nodes` are added later, once an ID has
// been allocated to all new features
nodes: [],
});
idMap[feature.type][feature.id] = entity.id;
graph = actionAddEntity(entity)(graph);
break;
}

case 'relation': {
const entity = osmRelation({
tags: feature.tags,
// `members` are added later, once an ID has
// been allocated to all new features
members: [],
});
idMap[feature.type][feature.id] = entity.id;
graph = actionAddEntity(entity)(graph);
break;
}

default:
// eslint-disable-next-line no-unused-expressions -- exhaustivity check
/** @satisfies {never} */ (feature);
}
}

// loop through the `create` features again, to set the loc/nodes/members
// we can also handle the `modify` features at the same time.
for (const feature of [...osmChange.create, ...osmChange.modify]) {
const entityId = getId(feature.type, feature.id, idMap);

// firstly, change tags
graph = actionChangeTags(entityId, feature.tags)(graph);

// secondly, change loc/nodes/members
switch (feature.type) {
case 'node':
graph = actionMoveNode(entityId, [feature.lon, feature.lat])(graph);
break;

case 'way': {
const newNodes = feature.nodes.map((id) => getId('node', id, idMap));
graph = actionReplaceWayNodes(entityId, newNodes)(graph);
break;
}

case 'relation': {
const newMembers = feature.members.map((member) => ({
id: getId(member.type, member.ref, idMap),
role: member.role,
type: member.type,
}));
graph = actionReplaceRelationMembers(entityId, newMembers)(graph);
break;
}

default:
// eslint-disable-next-line no-unused-expressions -- exhaustivity check
/** @satisfies {never} */ (feature);
}
}

// delete
for (const feature of osmChange.delete) {
const entityId = getId(feature.type, feature.id, idMap);
switch (feature.type) {
case 'node':
graph = actionDeleteNode(entityId)(graph);
break;

case 'way':
graph = actionDeleteWay(entityId)(graph);
break;

case 'relation':
graph = actionDeleteRelation(entityId)(graph);
break;

default:
// eslint-disable-next-line no-unused-expressions -- exhaustivity check
/** @satisfies {never} */ (feature);
}
}

return graph;
};
}
Loading
Loading