mirror of
https://github.com/FoggedLens/iD.git
synced 2026-02-14 09:42:56 +00:00
(closes #3324) Previously we allowed devs to swap out services that they didn't need. This became difficult now that ES6 exports are immutable bindings. But we can wrap the immutable bindings themselves in a live object, to get back the flexibility that we used to have. This change also drops the `taginfo` accessor on Context, since devs who want to swap out taginfo service with something else can now do so through the live binding. `iD.services.taginfo = myTaginfo()`
250 lines
6.9 KiB
JavaScript
250 lines
6.9 KiB
JavaScript
import * as d3 from 'd3';
|
|
import _ from 'lodash';
|
|
import { d3combobox } from '../../lib/d3.combobox.js';
|
|
import { dataAddressFormats } from '../../../data/index';
|
|
|
|
import {
|
|
geoExtent,
|
|
geoChooseEdge,
|
|
geoSphericalDistance
|
|
} from '../../geo/index';
|
|
|
|
import { services } from '../../services/index';
|
|
import { utilRebind } from '../../util/rebind';
|
|
import { utilGetSetValue } from '../../util/get_set_value';
|
|
|
|
|
|
export function uiFieldAddress(field, context) {
|
|
var dispatch = d3.dispatch('init', 'change'),
|
|
nominatim = services.nominatim,
|
|
wrap = d3.select(null),
|
|
isInitialized = false,
|
|
entity;
|
|
|
|
var widths = {
|
|
housenumber: 1/3,
|
|
street: 2/3,
|
|
city: 2/3,
|
|
state: 1/4,
|
|
postcode: 1/3
|
|
};
|
|
|
|
|
|
function getStreets() {
|
|
var extent = entity.extent(context.graph()),
|
|
l = extent.center(),
|
|
box = geoExtent(l).padByMeters(200);
|
|
|
|
return context.intersects(box)
|
|
.filter(isAddressable)
|
|
.map(function(d) {
|
|
var loc = context.projection([
|
|
(extent[0][0] + extent[1][0]) / 2,
|
|
(extent[0][1] + extent[1][1]) / 2]),
|
|
choice = geoChooseEdge(context.childNodes(d), loc, context.projection);
|
|
return {
|
|
title: d.tags.name,
|
|
value: d.tags.name,
|
|
dist: choice.distance
|
|
};
|
|
}).sort(function(a, b) {
|
|
return a.dist - b.dist;
|
|
});
|
|
|
|
function isAddressable(d) {
|
|
return d.tags.highway && d.tags.name && d.type === 'way';
|
|
}
|
|
}
|
|
|
|
|
|
function getCities() {
|
|
var extent = entity.extent(context.graph()),
|
|
l = extent.center(),
|
|
box = geoExtent(l).padByMeters(200);
|
|
|
|
return context.intersects(box)
|
|
.filter(isAddressable)
|
|
.map(function(d) {
|
|
return {
|
|
title: d.tags['addr:city'] || d.tags.name,
|
|
value: d.tags['addr:city'] || d.tags.name,
|
|
dist: geoSphericalDistance(d.extent(context.graph()).center(), l)
|
|
};
|
|
}).sort(function(a, b) {
|
|
return a.dist - b.dist;
|
|
});
|
|
|
|
function isAddressable(d) {
|
|
if (d.tags.name &&
|
|
(d.tags.admin_level === '8' || d.tags.border_type === 'city'))
|
|
return true;
|
|
|
|
if (d.tags.place && d.tags.name && (
|
|
d.tags.place === 'city' ||
|
|
d.tags.place === 'town' ||
|
|
d.tags.place === 'village'))
|
|
return true;
|
|
|
|
if (d.tags['addr:city']) return true;
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
function getPostCodes() {
|
|
var extent = entity.extent(context.graph()),
|
|
l = extent.center(),
|
|
box = geoExtent(l).padByMeters(200);
|
|
|
|
return context.intersects(box)
|
|
.filter(isAddressable)
|
|
.map(function(d) {
|
|
return {
|
|
title: d.tags['addr:postcode'],
|
|
value: d.tags['addr:postcode'],
|
|
dist: geoSphericalDistance(d.extent(context.graph()).center(), l)
|
|
};
|
|
}).sort(function(a, b) {
|
|
return a.dist - b.dist;
|
|
});
|
|
|
|
function isAddressable(d) {
|
|
return d.tags['addr:postcode'];
|
|
}
|
|
}
|
|
|
|
|
|
function initCallback(err, countryCode) {
|
|
if (err) return;
|
|
|
|
var addressFormat = _.find(dataAddressFormats, function (a) {
|
|
return a && a.countryCodes && _.includes(a.countryCodes, countryCode);
|
|
}) || _.first(dataAddressFormats);
|
|
|
|
function row(r) {
|
|
// Normalize widths.
|
|
var total = _.reduce(r, function(sum, field) {
|
|
return sum + (widths[field] || 0.5);
|
|
}, 0);
|
|
|
|
return r.map(function (field) {
|
|
return {
|
|
id: field,
|
|
width: (widths[field] || 0.5) / total
|
|
};
|
|
});
|
|
}
|
|
|
|
wrap.selectAll('div')
|
|
.data(addressFormat.format)
|
|
.enter()
|
|
.append('div')
|
|
.attr('class', 'addr-row')
|
|
.selectAll('input')
|
|
.data(row)
|
|
.enter()
|
|
.append('input')
|
|
.property('type', 'text')
|
|
.attr('placeholder', function (d) { return field.t('placeholders.' + d.id); })
|
|
.attr('class', function (d) { return 'addr-' + d.id; })
|
|
.style('width', function (d) { return d.width * 100 + '%'; });
|
|
|
|
// Update
|
|
wrap.selectAll('.addr-street')
|
|
.call(d3combobox()
|
|
.fetcher(function(value, callback) {
|
|
callback(getStreets());
|
|
}));
|
|
|
|
wrap.selectAll('.addr-city')
|
|
.call(d3combobox()
|
|
.fetcher(function(value, callback) {
|
|
callback(getCities());
|
|
}));
|
|
|
|
wrap.selectAll('.addr-postcode')
|
|
.call(d3combobox()
|
|
.fetcher(function(value, callback) {
|
|
callback(getPostCodes());
|
|
}));
|
|
|
|
wrap.selectAll('input')
|
|
.on('blur', change())
|
|
.on('change', change());
|
|
|
|
wrap.selectAll('input:not(.combobox-input)')
|
|
.on('input', change(true));
|
|
|
|
dispatch.call('init');
|
|
isInitialized = true;
|
|
}
|
|
|
|
|
|
function address(selection) {
|
|
isInitialized = false;
|
|
|
|
wrap = selection.selectAll('.preset-input-wrap')
|
|
.data([0]);
|
|
|
|
wrap = wrap.enter()
|
|
.append('div')
|
|
.attr('class', 'preset-input-wrap')
|
|
.merge(wrap);
|
|
|
|
|
|
if (nominatim && entity) {
|
|
var center = entity.extent(context.graph()).center();
|
|
nominatim.countryCode(center, initCallback);
|
|
}
|
|
}
|
|
|
|
|
|
function change(onInput) {
|
|
return function() {
|
|
var tags = {};
|
|
|
|
wrap.selectAll('input')
|
|
.each(function (field) {
|
|
tags['addr:' + field.id] = this.value || undefined;
|
|
});
|
|
|
|
dispatch.call('change', this, tags, onInput);
|
|
};
|
|
}
|
|
|
|
|
|
function updateTags(tags) {
|
|
utilGetSetValue(wrap.selectAll('input'), function (field) {
|
|
return tags['addr:' + field.id] || '';
|
|
});
|
|
}
|
|
|
|
|
|
address.entity = function(_) {
|
|
if (!arguments.length) return entity;
|
|
entity = _;
|
|
return address;
|
|
};
|
|
|
|
|
|
address.tags = function(tags) {
|
|
if (isInitialized) {
|
|
updateTags(tags);
|
|
} else {
|
|
dispatch.on('init', function () {
|
|
updateTags(tags);
|
|
});
|
|
}
|
|
};
|
|
|
|
|
|
address.focus = function() {
|
|
var node = wrap.selectAll('input').node();
|
|
if (node) node.focus();
|
|
};
|
|
|
|
|
|
return utilRebind(address, dispatch, 'on');
|
|
}
|