Skip to content

Commit 72a8409

Browse files
Merge branch 'openstreetmap:develop' into develop
2 parents ee15b27 + 63cdfd3 commit 72a8409

21 files changed

+848
-329
lines changed

.vscode/i18n-ally-custom-framework.yml

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ languageIds:
66
- typescriptreact
77

88
usageMatchRegex:
9+
- "[^\\w\\d]t\\.addOrUpdate\\(['\"`]({key})['\"`]" # matches t.addOrUpdate("{key}"
910
- "[^\\w\\d]t\\.append\\(['\"`]({key})['\"`]" # matches t.append("{key}"
1011
- "[^\\w\\d]t\\.html\\(['\"`]({key})['\"`]" # matches t.html("{key}"
1112
- "[^\\w\\d]t\\(['\"`]({key})['\"`]" # matches t.html("{key}"

CHANGELOG.md

+12-3
Original file line numberDiff line numberDiff line change
@@ -38,17 +38,26 @@ _Breaking developer changes, which may affect downstream projects or sites that
3838
# unreleased (v2.33.0-dev)
3939

4040
#### :sparkles: Usability & Accessibility
41+
* Allow searching for coordinates in localized number format in search box ([#10805])
4142
#### :scissors: Operations
4243
#### :camera: Street-Level
44+
* Add prev/next button to viewer for local georeferenced photos ([#10852], thanks [@0xatulpatil])
4345
#### :white_check_mark: Validation
4446
#### :bug: Bugfixes
45-
* fix some direction cones not appearing on railway tracks ([#10843], thanks [@k-yle])
47+
* Fix some direction cones not appearing on railway tracks ([#10843], thanks [@k-yle])
48+
* Better handling of rate limited API calls and other API errors ([#10299])
4649
#### :earth_asia: Localization
4750
#### :hourglass: Performance
4851
#### :mortar_board: Walkthrough / Help
4952
#### :hammer: Development
5053

54+
[#10805]: https://github.com/openstreetmap/iD/pull/10805
55+
[#10299]: https://github.com/openstreetmap/iD/issues/10299
5156
[#10843]: https://github.com/openstreetmap/iD/pull/10843
57+
[#10852]: https://github.com/openstreetmap/iD/issues/10852
58+
[@0xatulpatil]: https://github.com/0xatulpatil
59+
60+
5261

5362
# 2.32.0
5463
##### 2025-03-05
@@ -75,7 +84,7 @@ _Breaking developer changes, which may affect downstream projects or sites that
7584
* Revalidate ways that are added to or removed from relations ([#10786])
7685
* Preserve `crossing:markings` tag when fixing missing connection of crossing path and road ([#9586], thanks [@jtracey])
7786
* Add a dedicated description to fix waterway-road intersections by adding a _culvert_ ([#10778], thanks [@matkoniecz])
78-
* Separate tag-upgrade warnings from NSI suggestions ([#10800], thanks [@k-yle])
87+
* Separate tag-upgrade warnings from NSI suggestions ([#10801], thanks [@k-yle])
7988
#### :bug: Bugfixes
8089
* Prevent degenerate ways caused by deleting a corner of a triangle ([#10003], thanks [@k-yle])
8190
* Fix briefly disappearing data layer during background layer tile layer switching transition ([#10748])
@@ -119,7 +128,7 @@ _Breaking developer changes, which may affect downstream projects or sites that
119128
[#10776]: https://github.com/openstreetmap/iD/issues/10776
120129
[#10778]: https://github.com/openstreetmap/iD/issues/10778
121130
[#10798]: https://github.com/openstreetmap/iD/pull/10798
122-
[#10800]: https://github.com/openstreetmap/iD/pull/10800
131+
[#10801]: https://github.com/openstreetmap/iD/pull/10801
123132
[#10807]: https://github.com/openstreetmap/iD/issues/10807
124133
[@hlfan]: https://github.com/hlfan
125134
[@Deeptanshu-sankhwar]: https://github.com/Deeptanshu-sankhwar

css/60_photos.css

+15
Original file line numberDiff line numberDiff line change
@@ -681,3 +681,18 @@ label.streetside-hires {
681681
cursor: pointer;
682682
}
683683

684+
.photo-controls-local {
685+
display: flex;
686+
align-items: center;
687+
justify-content: center;
688+
gap: 4px;
689+
}
690+
.photo-controls-local button {
691+
padding:0 6px;
692+
pointer-events: initial;
693+
}
694+
695+
.photo-controls-local button:disabled {
696+
background: rgba(255,255,255,.25);
697+
}
698+

data/core.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -596,6 +596,7 @@ en:
596596
offline: The OpenStreetMap API is offline. Your edits are safe locally. Please come back later.
597597
readonly: The OpenStreetMap API is currently read-only. You can continue editing, but must wait to save your changes.
598598
rateLimit: The OpenStreetMap API is limiting anonymous connections. You can fix this by logging in.
599+
rateLimited: The OpenStreetMap API is limiting your connection, please wait.
599600
local_storage_full: You have made too many edits to back up. Consider saving your changes now.
600601
retry: Retry
601602
commit:

modules/core/context.js

-6
Original file line numberDiff line numberDiff line change
@@ -118,12 +118,6 @@ export function coreContext() {
118118
function afterLoad(cid, callback) {
119119
return (err, result) => {
120120
if (err) {
121-
// 400 Bad Request, 401 Unauthorized, 403 Forbidden..
122-
if (err.status === 400 || err.status === 401 || err.status === 403) {
123-
if (_connection) {
124-
_connection.logout();
125-
}
126-
}
127121
if (typeof callback === 'function') {
128122
callback(err);
129123
}

modules/osm/entity.js

+2
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ osmEntity.prototype = {
6060
/** @type {Tags} */
6161
tags: {},
6262

63+
/** @type {String} */
64+
id: undefined,
6365

6466
initialize: function(sources) {
6567
for (var i = 0; i < sources.length; ++i) {

modules/services/osm.js

+43-37
Original file line numberDiff line numberDiff line change
@@ -524,10 +524,6 @@ function updateRtree(item, replace) {
524524
function wrapcb(thisArg, callback, cid) {
525525
return function(err, result) {
526526
if (err) {
527-
// 401 Unauthorized, 403 Forbidden
528-
if (err.status === 401 || err.status === 403) {
529-
thisArg.logout();
530-
}
531527
return callback.call(thisArg, err);
532528

533529
} else if (thisArg.getConnectionId() !== cid) {
@@ -640,39 +636,23 @@ export default {
640636
return;
641637
}
642638

643-
var isAuthenticated = that.authenticated();
644-
645-
// 401 Unauthorized, 403 Forbidden
646-
// Logout and retry the request.
647-
if (isAuthenticated && err && err.status &&
648-
(err.status === 401 || err.status === 403)) {
649-
that.logout();
650-
that.loadFromAPI(path, callback, options);
651-
// else, no retry.
652-
} else {
653-
// 509 Bandwidth Limit Exceeded, 429 Too Many Requests
654-
// Set the rateLimitError flag and trigger a warning.
655-
if (!isAuthenticated && !_rateLimitError && err && err.status &&
656-
(err.status === 509 || err.status === 429)) {
657-
_rateLimitError = err;
658-
dispatch.call('change');
659-
that.reloadApiStatus();
660-
} else if ((err && _cachedApiStatus === 'online') ||
661-
(!err && _cachedApiStatus !== 'online')) {
662-
// If the response's error state doesn't match the status,
663-
// it's likely we lost or gained the connection so reload the status
664-
that.reloadApiStatus();
665-
}
639+
if ((err && _cachedApiStatus === 'online') ||
640+
(!err && _cachedApiStatus !== 'online')) {
641+
// If the response's error state doesn't match the status,
642+
// it's likely we lost or gained the connection so reload the status
643+
that.reloadApiStatus();
644+
}
666645

667-
if (callback) {
668-
if (err) {
669-
return callback(err);
646+
if (callback) {
647+
if (err) {
648+
// eslint-disable-next-line no-console
649+
console.error('API error:', err);
650+
return callback(err);
651+
} else {
652+
if (path.indexOf('.json') !== -1) {
653+
return parseJSON(payload, callback, options);
670654
} else {
671-
if (path.indexOf('.json') !== -1) {
672-
return parseJSON(payload, callback, options);
673-
} else {
674-
return parseXML(payload, callback, options);
675-
}
655+
return parseXML(payload, callback, options);
676656
}
677657
}
678658
}
@@ -1098,6 +1078,12 @@ export default {
10981078
var hadRequests = hasInflightRequests(_tileCache);
10991079
abortUnwantedRequests(_tileCache, tiles);
11001080
if (hadRequests && !hasInflightRequests(_tileCache)) {
1081+
if (_rateLimitError) {
1082+
// was rate limited, but has settled
1083+
_rateLimitError = undefined;
1084+
dispatch.call('change');
1085+
this.reloadApiStatus();
1086+
}
11011087
dispatch.call('loaded'); // stop the spinner
11021088
}
11031089

@@ -1123,23 +1109,43 @@ export default {
11231109

11241110
_tileCache.inflight[tile.id] = this.loadFromAPI(
11251111
path + tile.extent.toParam(),
1126-
tileCallback,
1112+
tileCallback.bind(this),
11271113
options
11281114
);
11291115

11301116
function tileCallback(err, parsed) {
1131-
delete _tileCache.inflight[tile.id];
11321117
if (!err) {
1118+
delete _tileCache.inflight[tile.id];
11331119
delete _tileCache.toLoad[tile.id];
11341120
_tileCache.loaded[tile.id] = true;
11351121
var bbox = tile.extent.bbox();
11361122
bbox.id = tile.id;
11371123
_tileCache.rtree.insert(bbox);
1124+
} else {
1125+
// map tile loading error: e.g. network connection error,
1126+
// 509 Bandwidth Limit Exceeded, 429 Too Many Requests
1127+
if (!_rateLimitError && err.status === 509 || err.status === 429) {
1128+
// show "API rate limiting" warning
1129+
_rateLimitError = err;
1130+
dispatch.call('change');
1131+
this.reloadApiStatus();
1132+
}
1133+
setTimeout(() => {
1134+
// retry loading the tiles
1135+
delete _tileCache.inflight[tile.id];
1136+
this.loadTile(tile, callback);
1137+
}, 8000);
11381138
}
11391139
if (callback) {
11401140
callback(err, Object.assign({ data: parsed }, tile));
11411141
}
11421142
if (!hasInflightRequests(_tileCache)) {
1143+
if (_rateLimitError) {
1144+
// was rate limited, but has settled
1145+
_rateLimitError = undefined;
1146+
dispatch.call('change');
1147+
this.reloadApiStatus();
1148+
}
11431149
dispatch.call('loaded'); // stop the spinner
11441150
}
11451151
}

modules/svg/local_photos.js

+37
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export function svgLocalPhotos(projection, context, dispatch) {
1717
let _photos = [];
1818
let _idAutoinc = 0;
1919
let _photoFrame;
20+
let _activePhotoIdx;
2021

2122
function init() {
2223
if (_initialized) return; // run once
@@ -66,14 +67,43 @@ export function svgLocalPhotos(projection, context, dispatch) {
6667
.append('div')
6768
.attr('class', 'photo-attribution photo-attribution-dual fillD');
6869

70+
const controlsEnter = viewerEnter
71+
.append('div')
72+
.attr('class', 'photo-controls-wrap')
73+
.append('div')
74+
.attr('class', 'photo-controls-local');
75+
76+
controlsEnter
77+
.append('button')
78+
.classed('back', true)
79+
.on('click.back', () => stepPhotos(-1))
80+
.text('◀');
81+
82+
controlsEnter
83+
.append('button')
84+
.classed('forward', true)
85+
.on('click.forward', () => stepPhotos(1))
86+
.text('▶');
87+
6988
return planePhotoFrame.init(context, viewerEnter)
7089
.then(planePhotoFrame => {
7190
_photoFrame = planePhotoFrame;
7291
});
7392
}
7493

94+
function stepPhotos(stepBy){
95+
if (!_photos || _photos.length === 0) return;
96+
if (_activePhotoIdx === undefined) _activePhotoIdx = 0;
97+
98+
const newIndex = _activePhotoIdx + stepBy;
99+
_activePhotoIdx = Math.max(0, Math.min(_photos.length - 1, newIndex));
100+
101+
click(null, _photos[_activePhotoIdx], false);
102+
}
103+
75104
// opens the image at bottom left
76105
function click(d3_event, image, zoomTo) {
106+
_activePhotoIdx = _photos.indexOf(image);
77107
ensureViewerLoaded(context).then(() => {
78108
const viewer = context.container().select('.photoviewer')
79109
.datum(image)
@@ -82,6 +112,13 @@ export function svgLocalPhotos(projection, context, dispatch) {
82112
const viewerWrap = viewer.select('.local-photos-wrapper')
83113
.classed('hide', false);
84114

115+
const controlsWrap = viewer.select('.photo-controls-wrap');
116+
117+
controlsWrap.select('.back')
118+
.attr('disabled', _activePhotoIdx <= 0 ? true: null);
119+
controlsWrap.select('.forward')
120+
.attr('disabled', _activePhotoIdx >= _photos.length - 1 ? true: null);
121+
85122
const attribution = viewerWrap.selectAll('.photo-attribution').text('');
86123

87124
if (image.date) {

modules/ui/account.js

+9-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,15 @@ export function uiAccount(context) {
1212
if (!osm.authenticated()) { // logged out
1313
render(selection, null);
1414
} else {
15-
osm.userDetails((err, user) => render(selection, user));
15+
osm.userDetails((err, user) => {
16+
if (err && err.status === 401) {
17+
// 401 Unauthorized
18+
// cannot load own user data: there must be something wrong (e.g. API token was revoked)
19+
// -> log out to allow user to reauthenticate
20+
osm.logout();
21+
}
22+
render(selection, user);
23+
});
1624
}
1725
}
1826

modules/ui/feature_list.js

+7-5
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ export function uiFeatureList(context) {
120120
var result = [];
121121
var graph = context.graph();
122122
var visibleCenter = context.map().extent().center();
123-
var q = search.property('value').toLowerCase();
123+
var q = search.property('value').toLowerCase().trim();
124124

125125
if (!q) return result;
126126

@@ -132,16 +132,18 @@ export function uiFeatureList(context) {
132132

133133
const isLatLonValid = latLon[0] >= -90 && latLon[0] <= 90 && latLon[1] >= -180 && latLon[1] <= 180;
134134
let isLonLatValid = lonLat[0] >= -90 && lonLat[0] <= 90 && lonLat[1] >= -180 && lonLat[1] <= 180;
135-
isLonLatValid &&= !q.match(/[NSEW]/i);
136-
isLonLatValid &&= lonLat[0] !== lonLat[1];
135+
isLonLatValid &&= !q.match(/[NSEW]/i); // don't flip coords with explicit cardinal directions
136+
isLonLatValid &&= !locationMatch[2]; // don't flip zoom/x/y coords
137+
isLonLatValid &&= lonLat[0] !== lonLat[1]; // don't flip when lat=lon
137138

138139
if (isLatLonValid) {
139140
result.push({
140141
id: latLon[0] + '/' + latLon[1],
141142
geometry: 'point',
142143
type: t('inspector.location'),
143144
name: dmsCoordinatePair([latLon[1], latLon[0]]),
144-
location: latLon
145+
location: latLon,
146+
zoom: locationMatch[2]
145147
});
146148
}
147149
if (isLonLatValid) {
@@ -369,7 +371,7 @@ export function uiFeatureList(context) {
369371
d3_event.preventDefault();
370372

371373
if (d.location) {
372-
context.map().centerZoomEase([d.location[1], d.location[0]], 19);
374+
context.map().centerZoomEase([d.location[1], d.location[0]], d.zoom || 19);
373375

374376
} else if (d.entity) {
375377
utilHighlightEntities([d.id], false, context);

modules/ui/sections/background_display_options.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ export function uiSectionBackgroundDisplayOptions(context) {
7474
.attr('type', 'range')
7575
.attr('min', _minVal)
7676
.attr('max', _maxVal)
77-
.attr('step', '0.05')
77+
.attr('step', '0.01')
7878
.on('input', function(d3_event, d) {
7979
var val = d3_select(this).property('value');
8080
if (!val && d3_event && d3_event.target) {

modules/ui/splash.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export function uiSplash(context) {
1717
let updateMessage = '';
1818
const sawPrivacyVersion = prefs('sawPrivacyVersion');
1919
let showSplash = !prefs('sawSplash');
20-
if (sawPrivacyVersion !== context.privacyVersion) {
20+
if (sawPrivacyVersion && sawPrivacyVersion !== context.privacyVersion) {
2121
updateMessage = t('splash.privacy_update');
2222
showSplash = true;
2323
}

0 commit comments

Comments
 (0)