Skip to content
Open
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
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
#### :sparkles: Usability & Accessibility
* Increase number of displayed _recently used presets_ to 8 (from 4) ([#9545], thanks [@k-yle])
#### :scissors: Operations
* Show a preview of the result of some geometry opertions while the respective button in the edit menu is hovered ([#11778])

Check failure on line 44 in CHANGELOG.md

View workflow job for this annotation

GitHub Actions / Check for spelling errors

opertions ==> operations
#### :camera: Street-Level
#### :white_check_mark: Validation
* Make wording of fix for _overlapping features_ validator less ambiguous ([#9888], thanks [@k-yle)
Expand All @@ -60,6 +60,7 @@
##### 2026-03-02

#### :sparkles: Usability & Accessibility
* Add support for multiple custom background layers with full CRUD operations - add, edit, name, and delete custom backgrounds ([#8874], [#11850], thanks [@JaiswalShivang])
* Show warning when attempting to paste but nothing has been copied ([#9401], thanks [@omsaraykar])
* Don't suggest values from Taginfo for `addr:*` tags ([#11733], thanks [@k-yle])
* Don't suggest values from Taginfo for tags with less than 100 uses, even if they're documented on the wiki ([#11794], thanks [@Kaushik4141])
Expand Down Expand Up @@ -108,6 +109,10 @@


[#8464]: https://github.com/openstreetmap/iD/issues/8464
[#8874]: https://github.com/openstreetmap/iD/issues/8874
[#9401]: https://github.com/openstreetmap/iD/issues/9401
[#10935]: https://github.com/openstreetmap/iD/issues/10935
[#10999]: https://github.com/openstreetmap/iD/issues/10999
[#9226]: https://github.com/openstreetmap/iD/pull/9226
[#9401]: https://github.com/openstreetmap/iD/issues/9401
[#10935]: https://github.com/openstreetmap/iD/issues/10935
Expand All @@ -129,12 +134,14 @@
[#11759]: https://github.com/openstreetmap/iD/pull/11759
[#11773]:https://github.com/openstreetmap/iD/pull/11773
[#11774]: https://github.com/openstreetmap/iD/pull/11774
[#11783]: https://github.com/openstreetmap/iD/pull/11783
[#11784]: https://github.com/openstreetmap/iD/pull/11784
[#11794]: https://github.com/openstreetmap/iD/pull/11794
[#11799]: https://github.com/openstreetmap/iD/issues/11799
[#11783]: https://github.com/openstreetmap/iD/pull/11783
[#11804]: https://github.com/openstreetmap/iD/pull/11804
[#11819]: https://github.com/openstreetmap/iD/pull/11819
[#11850]: https://github.com/openstreetmap/iD/pull/11850
[#11861]: https://github.com/openstreetmap/iD/pull/11861
[#11862]: https://github.com/openstreetmap/iD/pull/11862
[#11869]: https://github.com/openstreetmap/iD/pull/11869
Expand Down
85 changes: 85 additions & 0 deletions css/80_app.css
Original file line number Diff line number Diff line change
Expand Up @@ -1447,6 +1447,7 @@ button.preset-reset .label.flash-bg {

.label-text .label-textannotation svg.icon {
margin: 0 8px;
color: #333;
opacity: 0.5;
width: 14px;
height: 14px;
Expand Down Expand Up @@ -6086,3 +6087,87 @@ textarea:focus:dir(rtl)::-webkit-resizer {
width: 100px;
color: var(--link-color);
}

.custom-backgrounds-header {
font-weight: bold;
font-size: 12px;
padding: 10px 0 6px 0;
margin-top: 12px;
color: var(--text-color);
}

.layer-custom-background-list {
margin: 0;
padding: 0;
}

.layer-custom-background-list li {
display: flex;
align-items: center;
padding: 4px 0;
}

.layer-custom-background-list li label {
flex: 1;
display: flex;
align-items: center;
cursor: pointer;
}

.layer-custom-background-list li label span {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}

.layer-custom-background-list .layer-browse,
.layer-custom-background-list .layer-delete {
flex-shrink: 0;
width: 28px;
height: 28px;
padding: 0;
display: flex;
align-items: center;
justify-content: center;
background: transparent;
border: none;
border-radius: 4px;
opacity: 0.6;
transition: opacity 0.15s, background-color 0.15s;
}

.layer-custom-background-list .layer-browse:hover,
.layer-custom-background-list .layer-delete:hover {
opacity: 1;
background: var(--bg-color-3);
}

.layer-custom-background-list .layer-delete:hover {
background: rgba(234, 0, 0, 0.15);
}

.layer-custom-background-list .layer-delete:hover .icon {
color: #ea0000;
}

.add-custom-background {
width: 100%;
padding: 8px 12px;
margin-top: 8px;
text-align: center;
background: var(--bg-color-3);
border: none;
border-radius: 4px;
font-size: 12px;
cursor: pointer;
transition: background-color 0.15s;
}

.add-custom-background:hover {
background: var(--passive-bg-color);
}

.settings-custom-background .field-name {
width: 100%;
margin-top: 10px;
}
8 changes: 8 additions & 0 deletions data/core.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -828,6 +828,10 @@ en:
best_imagery: Best known imagery source for this location
switch: Switch back to this background
custom: Custom
custom_backgrounds: Custom Backgrounds
add_custom: Add Custom...
delete_custom: Delete
edit_custom: Edit
overlays: Overlays
imagery_problem_faq: Report an Imagery Problem
reset: reset
Expand Down Expand Up @@ -1006,6 +1010,9 @@ en:
example: "Example:"
template:
placeholder: Enter a url template
name:
placeholder: Enter a name for this background
label: Name
custom_data:
tooltip: Edit custom data layer
header: Custom Map Data Settings
Expand Down Expand Up @@ -1087,6 +1094,7 @@ en:
confirm:
okay: "OK"
cancel: "Cancel"
delete_custom_background: "Delete this custom background?"
splash:
welcome: Welcome to the iD OpenStreetMap editor
text: "iD is a friendly but powerful tool for contributing to the world's best free world map. This is version {version}. For more information see {website} and report bugs at {github}."
Expand Down
66 changes: 60 additions & 6 deletions modules/renderer/background.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,17 @@ import turf_bbox from '@turf/bbox';
import whichPolygon from 'which-polygon';

import { prefs } from '../core/preferences';
import { t } from '../core/localizer';
import { fileFetcher } from '../core/file_fetcher';
import { geoMetersToOffset, geoOffsetToMeters, geoExtent } from '../geo';
import { rendererBackgroundSource } from './background_source';
import { rendererTileLayer } from './tile_layer';
import { utilQsString, utilStringQs } from '../util';
import { utilRebind } from '../util/rebind';

const PREF_CUSTOM_TEMPLATE_OLD = 'background-custom-template';
const PREF_CUSTOM_TEMPLATES = 'background-custom-templates';
export const CUSTOM_ID_PREFIX = 'custom-';

let _imageryIndex = null;

Expand Down Expand Up @@ -77,13 +81,37 @@ export function rendererBackground(context) {
// Add 'None'
_imageryIndex.backgrounds.unshift(rendererBackgroundSource.None());

// Add 'Custom'
let template = prefs('background-custom-template') || '';
const custom = rendererBackgroundSource.Custom(template);
_imageryIndex.backgrounds.unshift(custom);
function migrateCustomBackgrounds() {
const oldTemplate = prefs(PREF_CUSTOM_TEMPLATE_OLD);
let customTemplates;

return _imageryIndex;
try {
customTemplates = JSON.parse(prefs(PREF_CUSTOM_TEMPLATES) || '[]');
} catch {
customTemplates = [];
}

// Migrate from old single custom background
if (oldTemplate && customTemplates.length === 0) {
customTemplates = [{
id: CUSTOM_ID_PREFIX+'1',
name: t('background.custom'),
template: oldTemplate
}];
prefs(PREF_CUSTOM_TEMPLATES, JSON.stringify(customTemplates));
prefs(PREF_CUSTOM_TEMPLATE_OLD, null);
}

return customTemplates;
}
// Add custom
const customTemplates = migrateCustomBackgrounds();
customTemplates.forEach(function(entry) {
const customSource = rendererBackgroundSource.Custom(entry.id, entry.name, entry.template);
_imageryIndex.backgrounds.unshift(customSource);
});
return _imageryIndex;
});
}


Expand Down Expand Up @@ -205,6 +233,10 @@ export function rendererBackground(context) {
let id = currSource.id;
if (id === 'custom') {
id = `custom:${currSource.template()}`;
} else if (id && id.startsWith(CUSTOM_ID_PREFIX)) {
// Named custom backgrounds use custom:<template> in URL hash
// since the custom-N id is local to this browser
id = `custom:${currSource.template()}`;
}

if (id) {
Expand Down Expand Up @@ -346,6 +378,28 @@ export function rendererBackground(context) {
};


background.addCustomSource = (source) => {
if (!_imageryIndex) return;
// Remove existing source with same id if present
const idx = _imageryIndex.backgrounds.findIndex(d => d.id === source.id);
if (idx !== -1) {
_imageryIndex.backgrounds.splice(idx, 1, source);
} else {
// Insert after 'None' (index 0) and before other imagery
_imageryIndex.backgrounds.unshift(source);
}
};


background.removeCustomSource = (id) => {
if (!_imageryIndex) return;
const idx = _imageryIndex.backgrounds.findIndex(d => d.id === id);
if (idx !== -1) {
_imageryIndex.backgrounds.splice(idx, 1);
}
};


background.bing = () => {
background.baseLayerSource(background.findSource('Bing'));
};
Expand Down Expand Up @@ -482,7 +536,7 @@ export function rendererBackground(context) {
const template = requestedBackground.replace(/^custom:/, '');
const custom = background.findSource('custom');
background.baseLayerSource(custom.template(template));
prefs('background-custom-template', template);
prefs(PREF_CUSTOM_TEMPLATE_OLD, template);
} else {
background.baseLayerSource(
background.findSource(requestedBackground) ||
Expand Down
13 changes: 11 additions & 2 deletions modules/renderer/background_source.js
Original file line number Diff line number Diff line change
Expand Up @@ -560,15 +560,24 @@ rendererBackgroundSource.None = function() {
};


rendererBackgroundSource.Custom = function(template) {
var source = rendererBackgroundSource({ id: 'custom', template: template });
rendererBackgroundSource.Custom = function(id, name, template) {
if (arguments.length === 1) {
template = id;
id = 'custom';
name = null;
}

var source = rendererBackgroundSource({ id: id || 'custom', template: template });
var _customName = name;


source.name = function() {
if (_customName) return _customName;
return t('background.custom');
};

source.label = function() {
if (_customName) return function(selection) { selection.text(_customName); };
return t.append('background.custom');
};

Expand Down
Loading