can :create, Note unless user
can [:read, :download], Changeset
+ can :read, ChangesetComment
can :read, Tracepoint
can :read, User
can :read, [Node, Way, Relation, OldNode, OldWay, OldRelation]
// Disable the button group and also the buttons to avoid
// inconsistent behaviour when zooming
- var editDisabled = zoom < 13;
+ const editDisabled = zoom < 13;
$("#edit_tab")
.tooltip({ placement: "bottom" })
.tooltip(editDisabled ? "enable" : "disable")
let moreItemWidth = 0;
function updateHeader() {
- var windowWidth = $(window).width();
+ const windowWidth = $(window).width();
if (windowWidth < breakpointWidth) {
$("body").addClass("small-nav");
$(document).ready(function () {
- var marker, map;
+ let marker, map;
function setLocation(e) {
const latlng = e.latlng.wrap();
$("#map").show();
$("#usemap").hide();
- var params = $("#map").data();
- var centre = [params.lat, params.lon];
- var position = $("html").attr("dir") === "rtl" ? "topleft" : "topright";
+ const params = $("#map").data();
+ const centre = [params.lat, params.lon];
+ const position = $("html").attr("dir") === "rtl" ? "topleft" : "topright";
map = L.map("map", {
attributionControl: false,
<% end %>
};
- var map = L.map("map");
+ const map = L.map("map");
map.attributionControl.setPrefix("");
map.removeControl(map.attributionControl);
},
onAdd: function (map) {
- var container = L.Control.Attribution.prototype.onAdd.call(this, map);
+ const container = L.Control.Attribution.prototype.onAdd.call(this, map);
map.on("moveend", this._update, this);
$(document).ready(function () {
- var params = OSM.params();
+ const params = OSM.params();
- var url = "/note/new";
+ let url = "/note/new";
if (!params.zoom) params.zoom = 17;
if (params.lat && params.lon) url += OSM.formatHash(params);
$(".icon.note").attr("href", url);
/* globals iD */
document.addEventListener("DOMContentLoaded", function () {
- var container = document.getElementById("id-container");
+ const container = document.getElementById("id-container");
if (typeof iD === "undefined" || !iD.utilDetect().support) {
container.innerHTML = "This editor is supported " +
"Please upgrade your browser or use JOSM to edit the map.";
container.className = "unsupported";
} else {
- var idContext = iD.coreContext();
+ const idContext = iD.coreContext();
idContext.connection().apiConnections([]);
- var url = location.protocol + "//" + location.host;
+ const url = location.protocol + "//" + location.host;
idContext.preauth({
url: url,
apiUrl: url === "https://www.openstreetmap.org" ? "https://api.openstreetmap.org" : url,
access_token: container.dataset.token
});
- var id = idContext
+ const id = idContext
.embed(true)
.assetPath("iD/")
.assetMap(JSON.parse(container.dataset.assetMap))
return;
}
- var hashChangedAutomatically = false;
+ let hashChangedAutomatically = false;
id.map().on("move.embed", parent.$.throttle(250, function () {
if (id.inIntro()) return;
- var zoom = ~~id.map().zoom(),
- center = id.map().center(),
- llz = { lon: center[0], lat: center[1], zoom: zoom };
+ const zoom = ~~id.map().zoom(),
+ center = id.map().center(),
+ llz = { lon: center[0], lat: center[1], zoom: zoom };
parent.updateLinks(llz, zoom);
// Manually resolve URL to avoid iframe JS context weirdness.
// https://gist.github.com/jfirebaugh/5439412
- var hash = parent.OSM.formatHash(llz);
+ const hash = parent.OSM.formatHash(llz);
if (hash !== parent.location.hash) {
hashChangedAutomatically = true;
parent.location.replace(parent.location.href.replace(/(#.*|$)/, hash));
parent.$("body").on("click", "a.set_position", function (e) {
e.preventDefault();
- var data = parent.$(this).data();
+ const data = parent.$(this).data();
goToLocation(data);
});
return;
}
e.preventDefault();
- var data = parent.OSM.mapParams();
+ const data = parent.OSM.mapParams();
goToLocation(data);
});
}
//= require router
$(document).ready(function () {
- var map = new L.OSM.Map("map", {
+ const map = new L.OSM.Map("map", {
zoomControl: false,
layerControl: false,
contextmenu: true,
});
OSM.loadSidebarContent = function (path, callback) {
- var content_path = path;
+ let content_path = path;
map.setSidebarOverlaid(false);
const token = $("head").data("oauthToken");
if (token) OSM.oauth = { authorization: "Bearer " + token };
- var params = OSM.mapParams();
+ const params = OSM.mapParams();
map.attributionControl.setPrefix("");
}
});
- var sidebar = L.OSM.sidebar("#map-ui")
+ const sidebar = L.OSM.sidebar("#map-ui")
.addTo(map);
- var position = $("html").attr("dir") === "rtl" ? "topleft" : "topright";
+ const position = $("html").attr("dir") === "rtl" ? "topleft" : "topright";
function addControlGroup(controls) {
for (const control of controls) control.addTo(map);
- var firstContainer = controls[0].getContainer();
+ const firstContainer = controls[0].getContainer();
$(firstContainer).find(".control-button").first()
.addClass("control-button-first");
- var lastContainer = controls[controls.length - 1].getContainer();
+ const lastContainer = controls[controls.length - 1].getContainer();
$(lastContainer).find(".control-button").last()
.addClass("control-button-last");
}
$(".leaflet-control .control-button").tooltip({ placement: "left", container: "body" });
- var expiry = new Date();
+ const expiry = new Date();
expiry.setYear(expiry.getFullYear() + 10);
map.on("moveend baselayerchange overlayadd overlayremove", function () {
Cookies.set("_osm_welcome", "hide", { secure: true, expires: expiry, path: "/", samesite: "lax" });
});
- var bannerExpiry = new Date();
+ const bannerExpiry = new Date();
bannerExpiry.setYear(bannerExpiry.getFullYear() + 1);
$("#banner .btn-close").on("click", function (e) {
- var cookieId = e.target.id;
+ const cookieId = e.target.id;
$("#banner").hide();
e.preventDefault();
if (cookieId) {
if (OSM.MATOMO) {
map.on("baselayerchange overlayadd", function (e) {
if (e.layer.options) {
- var goal = OSM.MATOMO.goals[e.layer.options.layerId];
+ const goal = OSM.MATOMO.goals[e.layer.options.layerId];
if (goal) {
$("body").trigger("matomogoal", goal);
}
function remoteEditHandler(bbox, object) {
- var remoteEditHost = "http://127.0.0.1:8111",
- osmHost = location.protocol + "//" + location.host,
- query = new URLSearchParams({
- left: bbox.getWest() - 0.0001,
- top: bbox.getNorth() + 0.0001,
- right: bbox.getEast() + 0.0001,
- bottom: bbox.getSouth() - 0.0001
- });
+ const remoteEditHost = "http://127.0.0.1:8111",
+ osmHost = location.protocol + "//" + location.host,
+ query = new URLSearchParams({
+ left: bbox.getWest() - 0.0001,
+ top: bbox.getNorth() + 0.0001,
+ right: bbox.getEast() + 0.0001,
+ bottom: bbox.getSouth() - 0.0001
+ });
if (object && object.type !== "note") query.set("select", object.type + object.id); // can't select notes
sendRemoteEditCommand(remoteEditHost + "/load_and_zoom?" + query, function () {
}
$("a[data-editor=remote]").click(function (e) {
- var params = OSM.mapParams(this.search);
+ const params = OSM.mapParams(this.search);
remoteEditHandler(map.getBounds(), params.object);
e.preventDefault();
});
}
OSM.Index = function (map) {
- var page = {};
+ const page = {};
page.pushstate = page.popstate = function () {
map.setSidebarOverlaid(true);
};
OSM.Browse = function (map, type) {
- var page = {};
+ const page = {};
- page.pushstate = page.popstate = function (path, id) {
+ page.pushstate = page.popstate = function (path, id, version) {
OSM.loadSidebarContent(path, function () {
- addObject(type, id);
+ addObject(type, id, version);
});
};
- page.load = function (path, id) {
- addObject(type, id, true);
+ page.load = function (path, id, version) {
+ addObject(type, id, version, true);
};
- function addObject(type, id, center) {
- var hashParams = OSM.parseHash(window.location.hash);
- map.addObject({ type: type, id: parseInt(id, 10) }, function (bounds) {
+ function addObject(type, id, version, center) {
+ const hashParams = OSM.parseHash(window.location.hash);
+ map.addObject({ type: type, id: parseInt(id, 10), version: version && parseInt(version, 10) }, function (bounds) {
if (!hashParams.center && bounds.isValid() &&
(center || !map.getBounds().contains(bounds))) {
OSM.router.withoutMoveListener(function () {
};
OSM.OldBrowse = function () {
- var page = {};
+ const page = {};
page.pushstate = page.popstate = function (path) {
OSM.loadSidebarContent(path);
return page;
};
- var history = OSM.History(map);
+ const history = OSM.History(map);
OSM.router = OSM.Router(map, {
"/": OSM.Index(map),
"/user/:display_name/history": history,
"/note/:id": OSM.Note(map),
"/node/:id(/history)": OSM.Browse(map, "node"),
- "/node/:id/history/:version": OSM.OldBrowse(),
+ "/node/:id/history/:version": OSM.Browse(map, "node"),
"/way/:id(/history)": OSM.Browse(map, "way"),
"/way/:id/history/:version": OSM.OldBrowse(),
"/relation/:id(/history)": OSM.Browse(map, "relation"),
OSM.Changeset = function (map) {
- var page = {},
- content = $("#sidebar_content");
+ const page = {},
+ content = $("#sidebar_content");
page.pushstate = page.popstate = function (path) {
OSM.loadSidebarContent(path, function () {
const changesetData = content.find("[data-changeset]").data("changeset");
changesetData.type = "changeset";
- var hashParams = OSM.parseHash(window.location.hash);
+ const hashParams = OSM.parseHash(window.location.hash);
initialize();
map.addObject(changesetData, function (bounds) {
if (!hashParams.center && bounds.isValid()) {
function initialize() {
content.find("button[data-method][data-url]").on("click", function (e) {
e.preventDefault();
- var data = $(e.target).data();
- var include_data = e.target.name === "comment";
+ const data = $(e.target).data();
+ const include_data = e.target.name === "comment";
updateChangeset(data.method, data.url, include_data);
});
return $(input).val();
}
- var updateMenu = function updateMenu() {
+ const updateMenu = function updateMenu() {
map.contextmenu.setDisabled(2, map.getZoom() < 12);
map.contextmenu.setDisabled(4, map.getZoom() < 14);
};
OSM.DirectionsEndpoint = function Endpoint(map, input, iconUrl, dragCallback, changeCallback) {
- var endpoint = {};
+ const endpoint = {};
endpoint.marker = L.marker([0, 0], {
icon: L.icon({
function inputChangeListener(e) {
// make text the same in both text boxes
- var value = e.target.value;
+ const value = e.target.value;
endpoint.setValue(value);
}
delete endpoint.geocodeRequest;
input.removeClass("is-invalid");
- var coordinatesMatch = value.match(/^\s*([+-]?\d+(?:\.\d*)?)(?:\s+|\s*[/,]\s*)([+-]?\d+(?:\.\d*)?)\s*$/);
- var latlng = coordinatesMatch && L.latLng(coordinatesMatch[1], coordinatesMatch[2]);
+ const coordinatesMatch = value.match(/^\s*([+-]?\d+(?:\.\d*)?)(?:\s+|\s*[/,]\s*)([+-]?\d+(?:\.\d*)?)\s*$/);
+ const latlng = coordinatesMatch && L.latLng(coordinatesMatch[1], coordinatesMatch[2]);
if (latlng && endpoint.cachedReverseGeocode && endpoint.cachedReverseGeocode.latlng.equals(latlng)) {
setLatLng(latlng);
};
endpoint.swapCachedReverseGeocodes = function (otherEndpoint) {
- var g0 = endpoint.cachedReverseGeocode;
- var g1 = otherEndpoint.cachedReverseGeocode;
+ const g0 = endpoint.cachedReverseGeocode;
+ const g1 = otherEndpoint.cachedReverseGeocode;
delete endpoint.cachedReverseGeocode;
delete otherEndpoint.cachedReverseGeocode;
if (g0) otherEndpoint.cachedReverseGeocode = g0;
OSM.Directions = function (map) {
let controller = null; // the AbortController for the current route request if a route request is in progress
- var chosenEngine;
+ let chosenEngine;
- var popup = L.popup({ autoPanPadding: [100, 100] });
+ const popup = L.popup({ autoPanPadding: [100, 100] });
- var polyline = L.polyline([], {
+ const polyline = L.polyline([], {
color: "#03f",
opacity: 0.3,
weight: 10
});
- var highlight = L.polyline([], {
+ const highlight = L.polyline([], {
color: "#ff0",
opacity: 0.5,
weight: 12
});
- var endpointDragCallback = function (dragging) {
+ const endpointDragCallback = function (dragging) {
if (!map.hasLayer(polyline)) return;
if (dragging && !chosenEngine.draggable) return;
if (dragging && controller) return;
getRoute(false, !dragging);
};
- var endpointChangeCallback = function () {
+ const endpointChangeCallback = function () {
getRoute(true, true);
};
- var endpoints = [
+ const endpoints = [
OSM.DirectionsEndpoint(map, $("input[name='route_from']"), OSM.MARKER_GREEN, endpointDragCallback, endpointChangeCallback),
OSM.DirectionsEndpoint(map, $("input[name='route_to']"), OSM.MARKER_RED, endpointDragCallback, endpointChangeCallback)
];
- var expiry = new Date();
+ const expiry = new Date();
expiry.setYear(expiry.getFullYear() + 10);
- var engines = OSM.Directions.engines;
+ const engines = OSM.Directions.engines;
engines.sort(function (a, b) {
- var localised_a = I18n.t("javascripts.directions.engines." + a.id),
- localised_b = I18n.t("javascripts.directions.engines." + b.id);
+ const localised_a = I18n.t("javascripts.directions.engines." + a.id),
+ localised_b = I18n.t("javascripts.directions.engines." + b.id);
return localised_a.localeCompare(localised_b);
});
- var select = $("select.routing_engines");
+ const select = $("select.routing_engines");
engines.forEach(function (engine, i) {
select.append("<option value='" + i + "'>" + I18n.t("javascripts.directions.engines." + engine.id) + "</option>");
});
$(".directions_form .reverse_directions").on("click", function () {
- var coordFrom = endpoints[0].latlng,
- coordTo = endpoints[1].latlng,
- routeFrom = "",
+ const coordFrom = endpoints[0].latlng,
+ coordTo = endpoints[1].latlng;
+ let routeFrom = "",
routeTo = "";
if (coordFrom) {
routeFrom = coordFrom.lat + "," + coordFrom.lng;
}
function formatTime(s) {
- var m = Math.round(s / 60);
- var h = Math.floor(m / 60);
+ let m = Math.round(s / 60);
+ const h = Math.floor(m / 60);
m -= h * 60;
return h + ":" + (m < 10 ? "0" : "") + m;
}
map.fitBounds(polyline.getBounds().pad(0.05));
}
- var distanceText = $("<p>").append(
+ const distanceText = $("<p>").append(
I18n.t("javascripts.directions.distance") + ": " + formatDistance(route.distance) + ". " +
I18n.t("javascripts.directions.time") + ": " + formatTime(route.time) + ".");
if (typeof route.ascend !== "undefined" && typeof route.descend !== "undefined") {
I18n.t("javascripts.directions.descend") + ": " + formatHeight(route.descend) + ".");
}
- var turnByTurnTable = $("<table class='table table-hover table-sm mb-3'>")
+ const turnByTurnTable = $("<table class='table table-hover table-sm mb-3'>")
.append($("<tbody>"));
- var directionsCloseButton = $("<button type='button' class='btn-close'>")
+ const directionsCloseButton = $("<button type='button' class='btn-close'>")
.attr("aria-label", I18n.t("javascripts.close"));
$("#sidebar_content")
route.steps.forEach(function (step) {
const [ll, direction, instruction, dist, lineseg] = step;
- var row = $("<tr class='turn'/>");
+ const row = $("<tr class='turn'/>");
row.append("<td class='border-0'><div class='direction i" + direction + "'/></td> ");
row.append("<td>" + instruction);
row.append("<td class='distance text-body-secondary text-end'>" + getDistText(dist));
}
}
- var chosenEngineIndex = findEngine("fossgis_osrm_car");
+ let chosenEngineIndex = findEngine("fossgis_osrm_car");
if (Cookies.get("_osm_directions_engine")) {
chosenEngineIndex = findEngine(Cookies.get("_osm_directions_engine"));
}
});
$(".routing_marker_column img").on("dragstart", function (e) {
- var dt = e.originalEvent.dataTransfer;
+ const dt = e.originalEvent.dataTransfer;
dt.effectAllowed = "move";
- var dragData = { type: $(this).data("type") };
+ const dragData = { type: $(this).data("type") };
dt.setData("text", JSON.stringify(dragData));
if (dt.setDragImage) {
- var img = $("<img>").attr("src", $(e.originalEvent.target).attr("src"));
+ const img = $("<img>").attr("src", $(e.originalEvent.target).attr("src"));
dt.setDragImage(img.get(0), 12, 21);
}
});
- var page = {};
+ const page = {};
page.pushstate = page.popstate = function () {
$(".search_form").hide();
$("#map").on("drop", function (e) {
e.preventDefault();
- var oe = e.originalEvent;
- var dragData = JSON.parse(oe.dataTransfer.getData("text"));
- var type = dragData.type;
- var pt = L.DomEvent.getMousePosition(oe, map.getContainer()); // co-ordinates of the mouse pointer at present
+ const oe = e.originalEvent;
+ const dragData = JSON.parse(oe.dataTransfer.getData("text"));
+ const type = dragData.type;
+ const pt = L.DomEvent.getMousePosition(oe, map.getContainer()); // co-ordinates of the mouse pointer at present
pt.y += 20;
- var ll = map.containerPointToLatLng(pt);
+ const ll = map.containerPointToLatLng(pt);
const llWithPrecision = OSM.cropLocation(ll, map.getZoom());
endpoints[type === "from" ? 0 : 1].setValue(llWithPrecision.join(", "));
});
route = (params.get("route") || "").split(";");
if (params.has("engine")) {
- var engineIndex = findEngine(params.get("engine"));
+ const engineIndex = findEngine(params.get("engine"));
if (engineIndex >= 0) {
setEngine(engineIndex);
OSM.Export = function (map) {
- var page = {};
+ const page = {};
- var locationFilter = new L.LocationFilter({
+ const locationFilter = new L.LocationFilter({
enableButton: false,
adjustButton: false
}).on("change", update);
}
function boundsChanged() {
- var bounds = getBounds();
+ const bounds = getBounds();
map.fitBounds(bounds);
locationFilter.setBounds(bounds);
locationFilter.enable();
//= require jquery-simulate/jquery.simulate
OSM.History = function (map) {
- var page = {};
+ const page = {};
$("#sidebar_content")
.on("click", ".changeset_more a", loadMore)
unHighlightChangeset($(this).data("changeset").id);
});
- var group = L.featureGroup()
+ const group = L.featureGroup()
.on("mouseover", function (e) {
highlightChangeset(e.layer.id);
})
};
function highlightChangeset(id) {
- var layer = group.getLayer(id);
+ const layer = group.getLayer(id);
if (layer) layer.setStyle({ fillOpacity: 0.3, color: "#FF6600", weight: 3 });
$("#changeset_" + id).addClass("selected");
}
function unHighlightChangeset(id) {
- var layer = group.getLayer(id);
+ const layer = group.getLayer(id);
if (layer) layer.setStyle({ fillOpacity: 0, color: "#FF9500", weight: 2 });
$("#changeset_" + id).removeClass("selected");
}
function displayMoreChangesets(html) {
$("#sidebar_content .changeset_more").replaceWith(html);
- var oldList = $("#sidebar_content .changesets ol").first();
- var newList = oldList.next("ol");
+ const oldList = $("#sidebar_content .changesets ol").first();
+ const newList = oldList.next("ol");
newList.children().appendTo(oldList);
newList.remove();
}
if (window.location.pathname === "/history") {
data.set("bbox", map.getBounds().wrap().toBBoxString());
- var feedLink = $("link[type=\"application/atom+xml\"]"),
- feedHref = feedLink.attr("href").split("?")[0];
+ const feedLink = $("link[type=\"application/atom+xml\"]"),
+ feedHref = feedLink.attr("href").split("?")[0];
feedLink.attr("href", feedHref + "?" + data);
}
e.preventDefault();
e.stopPropagation();
- var div = $(this).parents(".changeset_more");
+ const div = $(this).parents(".changeset_more");
$(this).hide();
div.find(".loader").show();
});
}
- var changesets = [];
+ let changesets = [];
function updateBounds() {
group.clearLayers();
for (const changeset of changesets) {
- var bottomLeft = map.project(L.latLng(changeset.bbox.minlat, changeset.bbox.minlon)),
- topRight = map.project(L.latLng(changeset.bbox.maxlat, changeset.bbox.maxlon)),
- width = topRight.x - bottomLeft.x,
- height = bottomLeft.y - topRight.y,
- minSize = 20; // Min width/height of changeset in pixels
+ const bottomLeft = map.project(L.latLng(changeset.bbox.minlat, changeset.bbox.minlon)),
+ topRight = map.project(L.latLng(changeset.bbox.maxlat, changeset.bbox.maxlon)),
+ width = topRight.x - bottomLeft.x,
+ height = bottomLeft.y - topRight.y,
+ minSize = 20; // Min width/height of changeset in pixels
if (width < minSize) {
bottomLeft.x -= ((minSize - width) / 2);
updateBounds();
if (window.location.pathname !== "/history") {
- var bounds = group.getBounds();
+ const bounds = group.getBounds();
if (bounds.isValid()) map.fitBounds(bounds);
}
}
});
function updateData() {
- var bounds = map.getBounds();
+ const bounds = map.getBounds();
if (!loadedBounds || !loadedBounds.contains(bounds)) {
getData();
}
}
function getData() {
- var bounds = map.getBounds();
- var url = "/api/" + OSM.API_VERSION + "/map.json?bbox=" + bounds.toBBoxString();
+ const bounds = map.getBounds();
+ const url = "/api/" + OSM.API_VERSION + "/map.json?bbox=" + bounds.toBBoxString();
/*
* Modern browsers are quite happy showing far more than 100 features in
.then(function (data) {
dataLayer.clearLayers();
- var features = dataLayer.buildFeatures(data);
+ const features = dataLayer.buildFeatures(data);
function addFeatures() {
$("#browse_status").empty();
const noteLayer = map.noteLayer;
let notes = {};
- var noteIcons = {
+ const noteIcons = {
"new": L.icon({
iconUrl: OSM.NEW_NOTE_MARKER,
iconSize: [25, 40],
});
function updateMarker(old_marker, feature) {
- var marker = old_marker;
+ let marker = old_marker;
if (marker) {
marker.setIcon(noteIcons[feature.properties.status]);
} else {
};
function loadNotes() {
- var bounds = map.getBounds();
- var size = bounds.getSize();
+ const bounds = map.getBounds();
+ const size = bounds.getSize();
if (size <= OSM.MAX_NOTE_REQUEST_AREA) {
- var url = "/api/" + OSM.API_VERSION + "/notes.json?bbox=" + bounds.toBBoxString();
+ const url = "/api/" + OSM.API_VERSION + "/notes.json?bbox=" + bounds.toBBoxString();
if (noteLoader) noteLoader.abort();
}
function success(json) {
- var oldNotes = notes;
+ const oldNotes = notes;
notes = {};
for (const feature of json.features) {
- var marker = oldNotes[feature.properties.id];
+ const marker = oldNotes[feature.properties.id];
delete oldNotes[feature.properties.id];
notes[feature.properties.id] = updateMarker(marker, feature);
}
- for (var id in oldNotes) {
+ for (const id in oldNotes) {
noteLayer.removeLayer(oldNotes[id]);
}
}
OSM.NewNote = function (map) {
- var noteLayer = map.noteLayer,
- content = $("#sidebar_content"),
- page = {},
- addNoteButton = $(".control-note .control-button"),
- newNoteMarker,
+ const noteLayer = map.noteLayer,
+ content = $("#sidebar_content"),
+ page = {},
+ addNoteButton = $(".control-note .control-button");
+ let newNoteMarker,
halo;
- var noteIcons = {
+ const noteIcons = {
"new": L.icon({
iconUrl: OSM.NEW_NOTE_MARKER,
iconSize: [25, 40],
});
function createNote(location, text, callback) {
- fetch("/api/0.6/notes.json?", {
+ fetch("/api/0.6/notes.json", {
method: "POST",
headers: { ...OSM.oauth },
body: new URLSearchParams({
}
function addCreatedNoteMarker(feature) {
- var marker = L.marker(feature.geometry.coordinates.reverse(), {
+ const marker = L.marker(feature.geometry.coordinates.reverse(), {
icon: noteIcons[feature.properties.status],
opacity: 0.9,
interactive: true
map.addLayer(noteLayer);
const params = new URLSearchParams(path.substring(path.indexOf("?")));
- var markerLatlng;
+ let markerLatlng;
if (params.has("lat") && params.has("lon")) {
markerLatlng = L.latLng(params.get("lat"), params.get("lon"));
createNote(location, text, (feature) => {
if (typeof OSM.user === "undefined") {
- var anonymousNotesCount = Number(Cookies.get("_osm_anonymous_notes_count")) || 0;
+ const anonymousNotesCount = Number(Cookies.get("_osm_anonymous_notes_count")) || 0;
Cookies.set("_osm_anonymous_notes_count", anonymousNotesCount + 1, { secure: true, expires: 30, path: "/", samesite: "lax" });
}
content.find("textarea").val("");
OSM.Note = function (map) {
- var content = $("#sidebar_content"),
- page = {};
+ const content = $("#sidebar_content"),
+ page = {};
- var noteIcons = {
+ const noteIcons = {
"new": L.icon({
iconUrl: OSM.NEW_NOTE_MARKER,
iconSize: [25, 40],
page.pushstate = page.popstate = function (path, id) {
OSM.loadSidebarContent(path, function () {
- initialize(path, id);
-
- var data = $(".details").data();
+ const data = $(".details").data();
if (!data) return;
- var latLng = L.latLng(data.coordinates.split(","));
- if (!map.getBounds().contains(latLng)) {
- OSM.router.withoutMoveListener(function () {
- map.setView(latLng, 15, { reset: true });
- });
- }
+ const latLng = L.latLng(data.coordinates.split(","));
+ initialize(path, id, map.getBounds().contains(latLng));
});
};
initialize(path, id);
};
- function initialize(path, id) {
+ function initialize(path, id, skipMoveToNote) {
content.find("button[name]").on("click", function (e) {
e.preventDefault();
const { url, method } = $(e.target).data(),
})
.then(() => {
OSM.loadSidebarContent(path, () => {
- initialize(path, id);
+ initialize(path, id, false);
});
})
.catch(error => {
content.find("textarea").val("").trigger("input");
- var data = $(".details").data();
+ const data = $(".details").data();
if (data) {
- var hashParams = OSM.parseHash(window.location.hash);
+ const hashParams = OSM.parseHash(window.location.hash);
map.addObject({
type: "note",
id: parseInt(id, 10),
latLng: L.latLng(data.coordinates.split(",")),
icon: noteIcons[data.status]
}, function () {
- if (!hashParams.center) {
- var latLng = L.latLng(data.coordinates.split(","));
+ if (!hashParams.center && !skipMoveToNote) {
+ const latLng = L.latLng(data.coordinates.split(","));
OSM.router.withoutMoveListener(function () {
map.setView(latLng, 15, { reset: true });
});
}
function updateButtons() {
- var resolveButton = content.find("button[name='close']");
- var commentButton = content.find("button[name='comment']");
+ const resolveButton = content.find("button[name='close']");
+ const commentButton = content.find("button[name='comment']");
content.find("button[name]").prop("disabled", false);
if (content.find("textarea").val() === "") {
OSM.Query = function (map) {
- var url = OSM.OVERPASS_URL,
- credentials = OSM.OVERPASS_CREDENTIALS,
- queryButton = $(".control-query .control-button"),
- uninterestingTags = ["source", "source_ref", "source:ref", "history", "attribution", "created_by", "tiger:county", "tiger:tlid", "tiger:upload_uuid", "KSJ2:curve_id", "KSJ2:lat", "KSJ2:lon", "KSJ2:coordinate", "KSJ2:filename", "note:ja"],
- marker;
+ const url = OSM.OVERPASS_URL,
+ credentials = OSM.OVERPASS_CREDENTIALS,
+ queryButton = $(".control-query .control-button"),
+ uninterestingTags = ["source", "source_ref", "source:ref", "history", "attribution", "created_by", "tiger:county", "tiger:tlid", "tiger:upload_uuid", "KSJ2:curve_id", "KSJ2:lat", "KSJ2:lon", "KSJ2:coordinate", "KSJ2:filename", "note:ja"];
+ let marker;
- var featureStyle = {
+ const featureStyle = {
color: "#FF6200",
weight: 4,
opacity: 1,
});
function showResultGeometry() {
- var geometry = $(this).data("geometry");
+ const geometry = $(this).data("geometry");
if (geometry) map.addLayer(geometry);
$(this).addClass("selected");
}
function hideResultGeometry() {
- var geometry = $(this).data("geometry");
+ const geometry = $(this).data("geometry");
if (geometry) map.removeLayer(geometry);
$(this).removeClass("selected");
}
function interestingFeature(feature) {
if (feature.tags) {
- for (var key in feature.tags) {
+ for (const key in feature.tags) {
if (uninterestingTags.indexOf(key) < 0) {
return true;
}
}
function featurePrefix(feature) {
- var tags = feature.tags;
- var prefix = "";
+ const tags = feature.tags;
+ let prefix = "";
if (tags.boundary === "administrative" && tags.admin_level) {
prefix = I18n.t("geocoder.search_osm_nominatim.admin_levels.level" + tags.admin_level, {
defaultValue: I18n.t("geocoder.search_osm_nominatim.prefix.boundary.administrative")
});
} else {
- var prefixes = I18n.t("geocoder.search_osm_nominatim.prefix");
- var key, value;
+ const prefixes = I18n.t("geocoder.search_osm_nominatim.prefix");
- for (key in tags) {
- value = tags[key];
+ for (const key in tags) {
+ const value = tags[key];
if (prefixes[key]) {
if (prefixes[key][value]) {
}
}
- for (key in tags) {
- value = tags[key];
+ for (const key in tags) {
+ const value = tags[key];
if (prefixes[key]) {
- var first = value.slice(0, 1).toUpperCase(),
- rest = value.slice(1).replace(/_/g, " ");
+ const first = value.slice(0, 1).toUpperCase(),
+ rest = value.slice(1).replace(/_/g, " ");
return first + rest;
}
}
function featureName(feature) {
- var tags = feature.tags,
- locales = OSM.preferred_languages;
+ const tags = feature.tags,
+ locales = OSM.preferred_languages;
for (const locale of locales) {
if (tags["name:" + locale]) {
}
function featureGeometry(feature) {
- var geometry;
+ let geometry;
if (feature.type === "node" && feature.lat && feature.lon) {
geometry = L.circleMarker([feature.lat, feature.lon], featureStyle);
}
function runQuery(latlng, radius, query, $section, merge, compare) {
- var $ul = $section.find("ul");
+ const $ul = $section.find("ul");
$ul.empty();
$section.show();
})
.then(response => response.json())
.then(function (results) {
- var elements;
+ let elements;
$section.find(".loader").hide();
if (merge) {
elements = results.elements.reduce(function (hash, element) {
- var key = element.type + element.id;
+ const key = element.type + element.id;
if ("geometry" in element) {
delete element.bounds;
}
for (const element of elements) {
if (!interestingFeature(element)) continue;
- var $li = $("<li>")
+ const $li = $("<li>")
.addClass("list-group-item list-group-item-action")
.text(featurePrefix(element) + " ")
.appendTo($ul);
}
function compareSize(feature1, feature2) {
- var width1 = feature1.bounds.maxlon - feature1.bounds.minlon,
- height1 = feature1.bounds.maxlat - feature1.bounds.minlat,
- area1 = width1 * height1,
- width2 = feature2.bounds.maxlat - feature2.bounds.minlat,
- height2 = feature2.bounds.maxlat - feature2.bounds.minlat,
- area2 = width2 * height2;
+ const width1 = feature1.bounds.maxlon - feature1.bounds.minlon,
+ height1 = feature1.bounds.maxlat - feature1.bounds.minlat,
+ area1 = width1 * height1,
+ width2 = feature2.bounds.maxlat - feature2.bounds.minlat,
+ height2 = feature2.bounds.maxlat - feature2.bounds.minlat,
+ area2 = width2 * height2;
return area1 - area2;
}
* for each object.
*/
function queryOverpass(lat, lng) {
- var latlng = L.latLng(lat, lng).wrap(),
- bounds = map.getBounds().wrap(),
- zoom = map.getZoom(),
- bbox = [bounds.getSouthWest(), bounds.getNorthEast()]
- .map(c => OSM.cropLocation(c, zoom))
- .join(),
- geombbox = "geom(" + bbox + ");",
- radius = 10 * Math.pow(1.5, 19 - zoom),
- around = "(around:" + radius + "," + lat + "," + lng + ")",
- nodes = "node" + around,
- ways = "way" + around,
- relations = "relation" + around,
- nearby = "(" + nodes + ";" + ways + ";);out tags " + geombbox + relations + ";out " + geombbox,
- isin = "is_in(" + lat + "," + lng + ")->.a;way(pivot.a);out tags bb;out ids " + geombbox + "relation(pivot.a);out tags bb;";
+ const latlng = L.latLng(lat, lng).wrap(),
+ bounds = map.getBounds().wrap(),
+ zoom = map.getZoom(),
+ bbox = [bounds.getSouthWest(), bounds.getNorthEast()]
+ .map(c => OSM.cropLocation(c, zoom))
+ .join(),
+ geombbox = "geom(" + bbox + ");",
+ radius = 10 * Math.pow(1.5, 19 - zoom),
+ around = "(around:" + radius + "," + lat + "," + lng + ")",
+ nodes = "node" + around,
+ ways = "way" + around,
+ relations = "relation" + around,
+ nearby = "(" + nodes + ";" + ways + ";);out tags " + geombbox + relations + ";out " + geombbox,
+ isin = "is_in(" + lat + "," + lng + ")->.a;way(pivot.a);out tags bb;out ids " + geombbox + "relation(pivot.a);out tags bb;";
$("#sidebar_content .query-intro")
.hide();
queryButton.removeClass("active");
}
- var page = {};
+ const page = {};
page.pushstate = page.popstate = function (path) {
OSM.loadSidebarContent(path, function () {
$(".search_form a.btn.switch_link").on("click", function (e) {
e.preventDefault();
- var query = $(this).closest("form").find("input[name=query]").val();
+ const query = $(this).closest("form").find("input[name=query]").val();
let search = "";
if (query) search = "?" + new URLSearchParams({ from: query });
OSM.router.route("/directions" + search + OSM.formatHash(map));
$(".search_form").on("submit", function (e) {
e.preventDefault();
$("header").addClass("closed");
- var query = $(this).find("input[name=query]").val();
+ const query = $(this).find("input[name=query]").val();
let search = "/";
if (query) search = "/search?" + new URLSearchParams({ query });
OSM.router.route(search + OSM.formatHash(map));
.on("mouseover", "li.search_results_entry:has(a.set_position)", showSearchResult)
.on("mouseout", "li.search_results_entry:has(a.set_position)", hideSearchResult);
- var markers = L.layerGroup().addTo(map);
+ const markers = L.layerGroup().addTo(map);
function clickSearchMore(e) {
e.preventDefault();
e.stopPropagation();
- var div = $(this).parents(".search_more"),
- csrf_param = $("meta[name=csrf-param]").attr("content"),
- csrf_token = $("meta[name=csrf-token]").attr("content"),
- params = new URLSearchParams();
+ const div = $(this).parents(".search_more"),
+ csrf_param = $("meta[name=csrf-param]").attr("content"),
+ csrf_token = $("meta[name=csrf-token]").attr("content"),
+ params = new URLSearchParams();
$(this).hide();
div.find(".loader").show();
}
function showSearchResult() {
- var marker = $(this).data("marker");
+ let marker = $(this).data("marker");
if (!marker) {
- var data = $(this).find("a.set_position").data();
+ const data = $(this).find("a.set_position").data();
marker = L.marker([data.lat, data.lon], { icon: OSM.getUserIcon() });
}
function hideSearchResult() {
- var marker = $(this).data("marker");
+ const marker = $(this).data("marker");
if (marker) {
markers.removeLayer(marker);
}
function clickSearchResult(e) {
- var data = $(this).data();
+ const data = $(this).data();
panToSearchResult(data);
e.stopPropagation();
}
- var page = {};
+ const page = {};
page.pushstate = page.popstate = function (path) {
const params = new URLSearchParams(path.substring(path.indexOf("?")));
page.load = function () {
$(".search_results_entry").each(function (index) {
- var entry = $(this),
- csrf_param = $("meta[name=csrf-param]").attr("content"),
- csrf_token = $("meta[name=csrf-token]").attr("content"),
- params = new URLSearchParams({
- zoom: map.getZoom(),
- minlon: map.getBounds().getWest(),
- minlat: map.getBounds().getSouth(),
- maxlon: map.getBounds().getEast(),
- maxlat: map.getBounds().getNorth()
- });
+ const entry = $(this),
+ csrf_param = $("meta[name=csrf-param]").attr("content"),
+ csrf_token = $("meta[name=csrf-token]").attr("content"),
+ params = new URLSearchParams({
+ zoom: map.getZoom(),
+ minlon: map.getBounds().getWest(),
+ minlat: map.getBounds().getSouth(),
+ maxlon: map.getBounds().getEast(),
+ maxlat: map.getBounds().getNorth()
+ });
params.set(csrf_param, csrf_token);
fetch(entry.data("href"), {
method: "POST",
entry.html(html);
// go to first result of first geocoder
if (index === 0) {
- var firstResult = entry.find("*[data-lat][data-lon]:first").first();
+ const firstResult = entry.find("*[data-lat][data-lon]:first").first();
if (firstResult.length) {
panToSearchResult(firstResult.data());
}
L.OSM.key = function (options) {
- var control = L.OSM.sidebarPane(options, "key", null, "javascripts.key.title");
+ const control = L.OSM.sidebarPane(options, "key", null, "javascripts.key.title");
control.onAddPane = function (map, button, $ui) {
- var $section = $("<div>")
+ const $section = $("<div>")
.attr("class", "p-3")
.appendTo($ui);
}
function updateButton() {
- var disabled = OSM.LAYERS_WITH_MAP_KEY.indexOf(map.getMapBaseLayerId()) === -1;
+ const disabled = OSM.LAYERS_WITH_MAP_KEY.indexOf(map.getMapBaseLayerId()) === -1;
button
.toggleClass("disabled", disabled)
.attr("data-bs-original-title",
}
function update() {
- var layerId = map.getMapBaseLayerId(),
- zoom = map.getZoom();
+ const layerId = map.getMapBaseLayerId(),
+ zoom = map.getZoom();
$(".mapkey-table-entry").each(function () {
- var data = $(this).data();
+ const data = $(this).data();
$(this).toggle(
layerId === data.layer &&
(!data.zoomMin || zoom >= data.zoomMin) &&
L.OSM.layers = function (options) {
- var control = L.OSM.sidebarPane(options, "layers", "javascripts.map.layers.title", "javascripts.map.layers.header");
+ const control = L.OSM.sidebarPane(options, "layers", "javascripts.map.layers.title", "javascripts.map.layers.header");
control.onAddPane = function (map, button, $ui, toggle) {
- var layers = options.layers;
+ const layers = options.layers;
- var baseSection = $("<div>")
+ const baseSection = $("<div>")
.attr("class", "base-layers d-grid gap-3 p-3 border-bottom border-secondary-subtle")
.appendTo($ui);
layers.forEach(function (layer, i) {
- var id = "map-ui-layer-" + i;
+ const id = "map-ui-layer-" + i;
- var buttonContainer = $("<div class='position-relative'>")
+ const buttonContainer = $("<div class='position-relative'>")
.appendTo(baseSection);
- var mapContainer = $("<div class='position-absolute top-0 start-0 bottom-0 end-0 z-0 bg-body-secondary'>")
+ const mapContainer = $("<div class='position-absolute top-0 start-0 bottom-0 end-0 z-0 bg-body-secondary'>")
.appendTo(buttonContainer);
- var input = $("<input type='radio' class='btn-check' name='layer'>")
+ const input = $("<input type='radio' class='btn-check' name='layer'>")
.prop("id", id)
.prop("checked", map.hasLayer(layer))
.appendTo(buttonContainer);
- var item = $("<label class='btn btn-outline-primary border-4 rounded-3 bg-transparent position-absolute p-0 h-100 w-100 overflow-hidden'>")
+ const item = $("<label class='btn btn-outline-primary border-4 rounded-3 bg-transparent position-absolute p-0 h-100 w-100 overflow-hidden'>")
.prop("for", id)
.append($("<span class='badge position-absolute top-0 start-0 rounded-top-0 rounded-start-0 py-1 px-2 bg-body bg-opacity-75 text-body text-wrap text-start fs-6 lh-base'>").append(layer.options.name))
.appendTo(buttonContainer);
map.whenReady(function () {
- var miniMap = L.map(mapContainer[0], { attributionControl: false, zoomControl: false, keyboard: false })
+ const miniMap = L.map(mapContainer[0], { attributionControl: false, zoomControl: false, keyboard: false })
.addLayer(new layer.constructor(layer.options));
miniMap.dragging.disable();
});
if (OSM.STATUS !== "api_offline" && OSM.STATUS !== "database_offline") {
- var overlaySection = $("<div>")
+ const overlaySection = $("<div>")
.attr("class", "overlay-layers p-3")
.appendTo($ui);
.attr("class", "text-body-secondary small mb-2")
.appendTo(overlaySection);
- var overlays = $("<ul class='list-unstyled form-check'>")
+ const overlays = $("<ul class='list-unstyled form-check'>")
.appendTo(overlaySection);
- var addOverlay = function (layer, name, maxArea) {
- var item = $("<li>")
+ const addOverlay = function (layer, name, maxArea) {
+ const item = $("<li>")
.appendTo(overlays);
if (name === "notes" || name === "data") {
.tooltip("disable");
}
- var label = $("<label>")
+ const label = $("<label>")
.attr("class", "form-check-label")
.appendTo(item);
- var checked = map.hasLayer(layer);
+ let checked = map.hasLayer(layer);
- var input = $("<input>")
+ const input = $("<input>")
.attr("type", "checkbox")
.attr("class", "form-check-input")
.prop("checked", checked)
});
map.on("zoomend", function () {
- var disabled = map.getBounds().getSize() >= maxArea;
+ const disabled = map.getBounds().getSize() >= maxArea;
$(input).prop("disabled", disabled);
if (disabled && $(input).is(":checked")) {
L.OSM.locate = function (options) {
- var control = L.control.locate({
+ const control = L.control.locate({
icon: "icon geolocate",
iconLoading: "icon geolocate",
strings: {
});
control.onAdd = function (map) {
- var container = Object.getPrototypeOf(this).onAdd.apply(this, [map]);
+ const container = Object.getPrototypeOf(this).onAdd.apply(this, [map]);
$(container)
.removeClass("leaflet-control-locate leaflet-bar")
.addClass("control-locate")
},
getLayersCode: function () {
- var layerConfig = "";
+ let layerConfig = "";
this.eachLayer(function (layer) {
if (layer.options && layer.options.code) {
layerConfig += layer.options.code;
[params.mlat, params.mlon] = OSM.cropLocation(marker.getLatLng(), this.getZoom());
}
- var url = window.location.protocol + "//" + OSM.SERVER_URL + "/",
- query = new URLSearchParams(params),
- hash = OSM.formatHash(this);
+ let url = window.location.protocol + "//" + OSM.SERVER_URL + "/";
+ const query = new URLSearchParams(params),
+ hash = OSM.formatHash(this);
if (query) url += "?" + query;
if (hash) url += hash;
},
getShortUrl: function (marker) {
- var zoom = this.getZoom(),
- latLng = marker && this.hasLayer(marker) ? marker.getLatLng().wrap() : this.getCenter().wrap(),
- str = window.location.protocol + "//" + window.location.hostname.replace(/^www\.openstreetmap\.org/i, "osm.org") + "/go/",
- char_array = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_~",
- x = Math.round((latLng.lng + 180.0) * ((1 << 30) / 90.0)),
- y = Math.round((latLng.lat + 90.0) * ((1 << 30) / 45.0)),
- // JavaScript only has to keep 32 bits of bitwise operators, so this has to be
- // done in two parts. each of the parts c1/c2 has 30 bits of the total in it
- // and drops the last 4 bits of the full 64 bit Morton code.
- c1 = interlace(x >>> 17, y >>> 17),
- c2 = interlace((x >>> 2) & 0x7fff, (y >>> 2) & 0x7fff),
- digit,
- i;
-
- for (i = 0; i < Math.ceil((zoom + 8) / 3.0) && i < 5; ++i) {
- digit = (c1 >> (24 - (6 * i))) & 0x3f;
+ const zoom = this.getZoom(),
+ latLng = marker && this.hasLayer(marker) ? marker.getLatLng().wrap() : this.getCenter().wrap(),
+ char_array = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_~",
+ x = Math.round((latLng.lng + 180.0) * ((1 << 30) / 90.0)),
+ y = Math.round((latLng.lat + 90.0) * ((1 << 30) / 45.0)),
+ // JavaScript only has to keep 32 bits of bitwise operators, so this has to be
+ // done in two parts. each of the parts c1/c2 has 30 bits of the total in it
+ // and drops the last 4 bits of the full 64 bit Morton code.
+ c1 = interlace(x >>> 17, y >>> 17),
+ c2 = interlace((x >>> 2) & 0x7fff, (y >>> 2) & 0x7fff);
+ let str = window.location.protocol + "//" + window.location.hostname.replace(/^www\.openstreetmap\.org/i, "osm.org") + "/go/";
+
+ for (let i = 0; i < Math.ceil((zoom + 8) / 3.0) && i < 5; ++i) {
+ const digit = (c1 >> (24 - (6 * i))) & 0x3f;
str += char_array.charAt(digit);
}
- for (i = 5; i < Math.ceil((zoom + 8) / 3.0); ++i) {
- digit = (c2 >> (24 - (6 * (i - 5)))) & 0x3f;
+ for (let i = 5; i < Math.ceil((zoom + 8) / 3.0); ++i) {
+ const digit = (c2 >> (24 - (6 * (i - 5)))) & 0x3f;
str += char_array.charAt(digit);
}
- for (i = 0; i < ((zoom + 8) % 3); ++i) str += "-";
+ for (let i = 0; i < ((zoom + 8) % 3); ++i) str += "-";
// Called to interlace the bits in x and y, making a Morton code.
function interlace(x, y) {
- var interlaced_x = x,
+ let interlaced_x = x,
interlaced_y = y;
interlaced_x = (interlaced_x | (interlaced_x << 8)) & 0x00ff00ff;
interlaced_x = (interlaced_x | (interlaced_x << 4)) & 0x0f0f0f0f;
}
const params = new URLSearchParams();
- var layers = this.getLayersCode().replace("M", "");
+ const layers = this.getLayersCode().replace("M", "");
if (layers) {
params.set("layers", layers);
},
addObject: function (object, callback) {
- var objectStyle = {
+ const objectStyle = {
color: "#FF6200",
weight: 4,
opacity: 1,
fillOpacity: 0.5
};
- var changesetStyle = {
+ const changesetStyle = {
weight: 4,
color: "#FF9500",
opacity: 1,
interactive: false
};
- var haloStyle = {
+ const haloStyle = {
weight: 2.5,
radius: 20,
fillOpacity: 0.5,
if (callback) callback(this._objectLayer.getBounds());
this.fire("overlayadd", { layer: this._objectLayer });
} else { // element handled by L.OSM.DataLayer
- var map = this;
+ const map = this;
this._objectLoader = new AbortController();
fetch(OSM.apiUrl(object), {
headers: { accept: "application/json" },
},
setSidebarOverlaid: function (overlaid) {
- var mediumDeviceWidth = window.getComputedStyle(document.documentElement).getPropertyValue("--bs-breakpoint-md");
- var isMediumDevice = window.matchMedia(`(max-width: ${mediumDeviceWidth})`).matches;
- var sidebarWidth = $("#sidebar").width();
- var sidebarHeight = $("#sidebar").height();
+ const mediumDeviceWidth = window.getComputedStyle(document.documentElement).getPropertyValue("--bs-breakpoint-md");
+ const isMediumDevice = window.matchMedia(`(max-width: ${mediumDeviceWidth})`).matches;
+ const sidebarWidth = $("#sidebar").width();
+ const sidebarHeight = $("#sidebar").height();
if (overlaid && !$("#content").hasClass("overlay-sidebar")) {
$("#content").addClass("overlay-sidebar");
this.invalidateSize({ pan: false });
_oldGetIconUrl: L.Icon.Default.prototype._getIconUrl,
_getIconUrl: function (name) {
- var url = this._oldGetIconUrl(name);
+ const url = this._oldGetIconUrl(name);
return L.Icon.Default.imageUrls[url];
}
});
OSM.isDarkMap = function () {
- var mapTheme = $("body").attr("data-map-theme");
+ const mapTheme = $("body").attr("data-map-theme");
if (mapTheme) return mapTheme === "dark";
- var siteTheme = $("html").attr("data-bs-theme");
+ const siteTheme = $("html").attr("data-bs-theme");
if (siteTheme) return siteTheme === "dark";
return window.matchMedia("(prefers-color-scheme: dark)").matches;
};
L.OSM.note = function (options) {
- var control = L.control(options);
+ const control = L.control(options);
control.onAdd = function (map) {
- var $container = $("<div>")
+ const $container = $("<div>")
.attr("class", "control-note");
- var link = $("<a>")
+ const link = $("<a>")
.attr("class", "control-button")
.attr("href", "#")
.html("<span class=\"icon note\"></span>")
map.on("zoomend", update);
function update() {
- var wasDisabled = link.hasClass("disabled"),
- isDisabled = OSM.STATUS === "database_offline" || map.getZoom() < 12;
+ const wasDisabled = link.hasClass("disabled"),
+ isDisabled = OSM.STATUS === "database_offline" || map.getZoom() < 12;
link
.toggleClass("disabled", isDisabled)
.attr("data-bs-original-title", I18n.t(isDisabled ?
L.OSM.query = function (options) {
- var control = L.control(options);
+ const control = L.control(options);
control.onAdd = function (map) {
- var $container = $("<div>")
+ const $container = $("<div>")
.attr("class", "control-query");
- var link = $("<a>")
+ const link = $("<a>")
.attr("class", "control-button")
.attr("href", "#")
.html("<span class=\"icon query\"></span>")
map.on("zoomend", update);
function update() {
- var wasDisabled = link.hasClass("disabled"),
- isDisabled = map.getZoom() < 14;
+ const wasDisabled = link.hasClass("disabled"),
+ isDisabled = map.getZoom() < 14;
link
.toggleClass("disabled", isDisabled)
.attr("data-bs-original-title", I18n.t(isDisabled ?
L.OSM.share = function (options) {
- var control = L.OSM.sidebarPane(options, "share", "javascripts.share.title", "javascripts.share.title"),
- marker = L.marker([0, 0], { draggable: true }),
- locationFilter = new L.LocationFilter({
- enableButton: false,
- adjustButton: false
- });
+ const control = L.OSM.sidebarPane(options, "share", "javascripts.share.title", "javascripts.share.title"),
+ marker = L.marker([0, 0], { draggable: true }),
+ locationFilter = new L.LocationFilter({
+ enableButton: false,
+ adjustButton: false
+ });
control.onAddPane = function (map, button, $ui) {
// Link / Embed
$("#content").addClass("overlay-right-sidebar");
- var $linkSection = $("<div>")
+ const $linkSection = $("<div>")
.attr("class", "share-link p-3 border-bottom border-secondary-subtle")
.appendTo($ui);
.text(I18n.t("javascripts.share.link"))
.appendTo($linkSection);
- var $form = $("<form>")
+ let $form = $("<form>")
.appendTo($linkSection);
$("<div>")
.on("click", "a", function (e) {
e.preventDefault();
if (!$(this).hasClass("btn-primary")) return;
- var id = "#" + $(this).attr("for");
+ const id = "#" + $(this).attr("for");
$(this).siblings("a")
.removeClass("active");
$(this).addClass("active");
// Geo URI
- var $geoUriSection = $("<div>")
+ const $geoUriSection = $("<div>")
.attr("class", "share-geo-uri p-3 border-bottom border-secondary-subtle")
.appendTo($ui);
// Image
- var $imageSection = $("<div>")
+ const $imageSection = $("<div>")
.attr("class", "share-image p-3")
.appendTo($ui);
.appendTo($form);
}
- var csrf_param = $("meta[name=csrf-param]").attr("content"),
- csrf_token = $("meta[name=csrf-token]").attr("content");
+ const csrf_param = $("meta[name=csrf-param]").attr("content"),
+ csrf_token = $("meta[name=csrf-token]").attr("content");
$("<input>")
.attr("name", csrf_param)
.attr("type", "hidden")
.appendTo($form);
- var args = {
+ const args = {
layer: "<span id=\"mapnik_image_layer\"></span>",
width: "<span id=\"mapnik_image_width\"></span>",
height: "<span id=\"mapnik_image_height\"></span>"
}
function escapeHTML(string) {
- var htmlEscapes = {
+ const htmlEscapes = {
"&": "&",
"<": "<",
">": ">",
function update() {
const layer = map.getMapBaseLayer();
- var canEmbed = Boolean(layer && layer.options.canEmbed);
- var bounds = map.getBounds();
+ const canEmbed = Boolean(layer && layer.options.canEmbed);
+ let bounds = map.getBounds();
$("#link_marker")
.prop("checked", map.hasLayer(marker));
});
if (map.hasLayer(marker)) {
- var latLng = marker.getLatLng().wrap();
+ const latLng = marker.getLatLng().wrap();
params.set("marker", latLng.lat + "," + latLng.lng);
}
bounds = locationFilter.getBounds();
}
- var scale = $("#mapnik_scale").val(),
- size = L.bounds(L.CRS.EPSG3857.project(bounds.getSouthWest()),
- L.CRS.EPSG3857.project(bounds.getNorthEast())).getSize(),
- maxScale = Math.floor(Math.sqrt(size.x * size.y / 0.3136));
+ let scale = $("#mapnik_scale").val();
+ const size = L.bounds(L.CRS.EPSG3857.project(bounds.getSouthWest()),
+ L.CRS.EPSG3857.project(bounds.getNorthEast())).getSize(),
+ maxScale = Math.floor(Math.sqrt(size.x * size.y / 0.3136));
$("#mapnik_minlon").val(bounds.getWest());
$("#mapnik_minlat").val(bounds.getSouth());
}
function getScale() {
- var bounds = map.getBounds(),
- centerLat = bounds.getCenter().lat,
- halfWorldMeters = 6378137 * Math.PI * Math.cos(centerLat * Math.PI / 180),
- meters = halfWorldMeters * (bounds.getEast() - bounds.getWest()) / 180,
- pixelsPerMeter = map.getSize().x / meters,
- metersPerPixel = 1 / (92 * 39.3701);
+ const bounds = map.getBounds(),
+ centerLat = bounds.getCenter().lat,
+ halfWorldMeters = 6378137 * Math.PI * Math.cos(centerLat * Math.PI / 180),
+ meters = halfWorldMeters * (bounds.getEast() - bounds.getWest()) / 180,
+ pixelsPerMeter = map.getSize().x / meters,
+ metersPerPixel = 1 / (92 * 39.3701);
return Math.round(1 / (pixelsPerMeter * metersPerPixel));
}
function roundScale(scale) {
- var precision = 5 * Math.pow(10, Math.floor(Math.LOG10E * Math.log(scale)) - 2);
+ const precision = 5 * Math.pow(10, Math.floor(Math.LOG10E * Math.log(scale)) - 2);
return precision * Math.ceil(scale / precision);
}
};
L.OSM.sidebarPane = function (options, uiClass, buttonTitle, paneTitle) {
- var control = L.control(options);
+ const control = L.control(options);
control.onAdd = function (map) {
- var $container = $("<div>")
+ const $container = $("<div>")
.attr("class", "control-" + uiClass);
- var button = $("<a>")
+ const button = $("<a>")
.attr("class", "control-button")
.attr("href", "#")
.html("<span class=\"icon " + uiClass + "\"></span>")
button.appendTo($container);
- var $ui = $("<div>")
+ const $ui = $("<div>")
.attr("class", uiClass + "-ui");
$("<div class='d-flex p-3 pb-0'>")
L.OSM.sidebar = function (selector) {
- var control = {},
- sidebar = $(selector),
- current = $(),
+ const control = {},
+ sidebar = $(selector);
+ let current = $(),
currentButton = $(),
map;
};
control.togglePane = function (pane, button) {
- var mediumDeviceWidth = window.getComputedStyle(document.documentElement).getPropertyValue("--bs-breakpoint-md");
- var isMediumDevice = window.matchMedia(`(max-width: ${mediumDeviceWidth})`).matches;
- var paneWidth = 250;
+ const mediumDeviceWidth = window.getComputedStyle(document.documentElement).getPropertyValue("--bs-breakpoint-md");
+ const isMediumDevice = window.matchMedia(`(max-width: ${mediumDeviceWidth})`).matches;
+ const paneWidth = 250;
current
.hide()
},
onAdd: function (map) {
- var zoomName = "zoom",
- container = L.DomUtil.create("div", zoomName);
+ const zoomName = "zoom",
+ container = L.DomUtil.create("div", zoomName);
this._map = map;
},
_createButton: function (html, title, className, container, fn, context) {
- var link = L.DomUtil.create("a", "control-button " + className, container);
+ const link = L.DomUtil.create("a", "control-button " + className, container);
link.innerHTML = html;
link.href = "#";
link.title = title;
L.DomUtil.create("span", "icon " + className, link);
- var stop = L.DomEvent.stopPropagation;
+ const stop = L.DomEvent.stopPropagation;
L.DomEvent
.on(link, "click", stop)
},
_updateDisabled: function () {
- var map = this._map,
- className = "disabled";
+ const map = this._map,
+ className = "disabled";
L.DomUtil.removeClass(this._zoomInButton, className);
L.DomUtil.removeClass(this._zoomOutButton, className);
if (OSM.MATOMO) {
$(document).ready(function () {
- var base = document.location.protocol + "//" + OSM.MATOMO.location + "/";
- var matomoTracker;
+ const base = document.location.protocol + "//" + OSM.MATOMO.location + "/";
+ let matomoTracker;
- var matomoLoader = $.ajax({
+ const matomoLoader = $.ajax({
url: base + "matomo.js",
dataType: "script",
cache: true,
CLOSED_NOTE_MARKER: <%= image_path("closed_note_marker.svg").to_json %>,
apiUrl: function (object) {
- var apiType = object.type === "note" ? "notes" : object.type;
- var url = "/api/" + OSM.API_VERSION + "/" + apiType + "/" + object.id;
+ const apiType = object.type === "note" ? "notes" : object.type;
+ let url = "/api/" + OSM.API_VERSION + "/" + apiType + "/" + object.id;
if (object.type === "way" || object.type === "relation") {
url += "/full";
},
params: function (search) {
- var query = search || window.location.search;
+ const query = search || window.location.search;
return Object.fromEntries(new URLSearchParams(query));
},
mapParams: function (search) {
- var params = OSM.params(search),
- mapParams = {};
+ const params = OSM.params(search),
+ mapParams = {};
if (params.mlon && params.mlat) {
mapParams.marker = true;
}
}
- var hash = OSM.parseHash(location.hash);
+ const hash = OSM.parseHash(location.hash);
const loc = Cookies.get("_osm_location")?.split("|");
mapParams.layers = hash.layers || (loc && loc[3]) || "";
- var scale = parseFloat(params.scale);
+ const scale = parseFloat(params.scale);
if (scale > 0) {
mapParams.zoom = Math.log(360.0 / (scale * 512.0)) / Math.log(2.0);
}
},
parseHash: function (hash) {
- var args = {};
+ const args = {};
- var i = hash.indexOf("#");
+ const i = hash.indexOf("#");
if (i < 0) {
return args;
}
const hashParams = new URLSearchParams(hash.slice(i + 1));
- var map = (hashParams.get("map") || "").split("/"),
- zoom = parseInt(map[0], 10),
- lat = parseFloat(map[1]),
- lon = parseFloat(map[2]);
+ const map = (hashParams.get("map") || "").split("/"),
+ zoom = parseInt(map[0], 10),
+ lat = parseFloat(map[1]),
+ lon = parseFloat(map[2]);
if (!isNaN(zoom) && !isNaN(lat) && !isNaN(lon)) {
args.center = new L.LatLng(lat, lon);
},
formatHash: function (args) {
- var center, zoom, layers;
+ let center, zoom, layers;
if (args instanceof L.Map) {
center = args.getCenter();
},
zoomPrecision: function (zoom) {
- var pixels = Math.pow(2, 8 + zoom);
- var degrees = 180;
+ const pixels = Math.pow(2, 8 + zoom);
+ const degrees = 180;
return Math.ceil(Math.log10(pixels / degrees));
},
},
distance: function (latlng1, latlng2) {
- var lat1 = latlng1.lat * Math.PI / 180,
- lng1 = latlng1.lng * Math.PI / 180,
- lat2 = latlng2.lat * Math.PI / 180,
- lng2 = latlng2.lng * Math.PI / 180,
- latdiff = lat2 - lat1,
- lngdiff = lng2 - lng1;
+ const lat1 = latlng1.lat * Math.PI / 180,
+ lng1 = latlng1.lng * Math.PI / 180,
+ lat2 = latlng2.lat * Math.PI / 180,
+ lng2 = latlng2.lng * Math.PI / 180,
+ latdiff = lat2 - lat1,
+ lngdiff = lng2 - lng1;
return 6372795 * 2 * Math.asin(
Math.sqrt(
* the user next switches to it.
*/
$(document).on("change", ".richtext_container textarea", function () {
- var container = $(this).closest(".richtext_container");
- var preview = container.find(".tab-pane[id$='_preview']");
+ const container = $(this).closest(".richtext_container");
+ const preview = container.find(".tab-pane[id$='_preview']");
preview.children(".richtext_placeholder").attr("hidden", true).removeClass("delayed-fade-in");
preview.children(".richtext").empty();
* when switching away from an edit pane
*/
$(document).on("hide.bs.tab", ".richtext_container button[data-bs-target$='_edit']", function () {
- var container = $(this).closest(".richtext_container");
- var editor = container.find("textarea");
- var preview = container.find(".tab-pane[id$='_preview']");
- var minHeight = editor.outerHeight() - preview.outerHeight() + preview.height();
+ const container = $(this).closest(".richtext_container");
+ const editor = container.find("textarea");
+ const preview = container.find(".tab-pane[id$='_preview']");
+ const minHeight = editor.outerHeight() - preview.outerHeight() + preview.height();
preview.css("min-height", minHeight + "px");
});
* Install a handler to switch to preview mode
*/
$(document).on("show.bs.tab", ".richtext_container button[data-bs-target$='_preview']", function () {
- var container = $(this).closest(".richtext_container");
- var editor = container.find("textarea");
- var preview = container.find(".tab-pane[id$='_preview']");
+ const container = $(this).closest(".richtext_container");
+ const editor = container.find("textarea");
+ const preview = container.find(".tab-pane[id$='_preview']");
if (preview.children(".richtext").contents().length === 0) {
preview.children(".richtext_placeholder").removeAttr("hidden").addClass("delayed-fade-in");
});
function invalidTextareaListener() {
- var container = $(this).closest(".richtext_container");
+ const container = $(this).closest(".richtext_container");
container.find("button[data-bs-target$='_edit']").tab("show");
}
function updateHelp() {
$(".richtext_container .richtext_help_sidebar:not(:visible):not(:empty)").each(function () {
- var container = $(this).closest(".richtext_container");
+ const container = $(this).closest(".richtext_container");
$(this).children().appendTo(container.find(".tab-pane[id$='_help']"));
});
$(".richtext_container .richtext_help_sidebar:visible:empty").each(function () {
- var container = $(this).closest(".richtext_container");
+ const container = $(this).closest(".richtext_container");
container.find(".tab-pane[id$='_help']").children().appendTo($(this));
if (container.find("button[data-bs-target$='_help'].active").length) {
container.find("button[data-bs-target$='_edit']").tab("show");
move the map without the hash changing.
*/
OSM.Router = function (map, rts) {
- var escapeRegExp = /[-{}[\]+?.,\\^$|#\s]/g;
- var optionalParam = /\((.*?)\)/g;
- var namedParam = /(\(\?)?:\w+/g;
- var splatParam = /\*\w+/g;
+ const escapeRegExp = /[-{}[\]+?.,\\^$|#\s]/g;
+ const optionalParam = /\((.*?)\)/g;
+ const namedParam = /(\(\?)?:\w+/g;
+ const splatParam = /\*\w+/g;
function Route(path, controller) {
- var regexp = new RegExp("^" +
+ const regexp = new RegExp("^" +
path.replace(escapeRegExp, "\\$&")
.replace(optionalParam, "(?:$1)?")
.replace(namedParam, function (match, optional) {
})
.replace(splatParam, "(.*?)") + "(?:\\?.*)?$");
- var route = {};
+ const route = {};
route.match = function (path) {
return regexp.test(path);
};
route.run = function (action, path) {
- var params = [];
+ let params = [];
if (path) {
params = regexp.exec(path).map(function (param, i) {
}
};
- var currentPath = window.location.pathname.replace(/(.)\/$/, "$1") + window.location.search,
+ let currentPath = window.location.pathname.replace(/(.)\/$/, "$1") + window.location.search,
currentRoute = routes.recognize(currentPath),
currentHash = location.hash || OSM.formatHash(map);
- var router = {};
+ const router = {};
function updateSecondaryNav() {
$("header nav.secondary > ul > li > a").each(function () {
- var active = $(this).attr("href") === window.location.pathname;
+ const active = $(this).attr("href") === window.location.pathname;
$(this)
.toggleClass("text-secondary", !active)
$(window).on("popstate", function (e) {
if (!e.originalEvent.state) return; // Is it a real popstate event or just a hash change?
- var path = window.location.pathname + window.location.search,
- route = routes.recognize(path);
+ const path = window.location.pathname + window.location.search,
+ route = routes.recognize(path);
if (path === currentPath) return;
currentRoute.run("unload", null, route === currentRoute);
currentPath = path;
});
router.route = function (url) {
- var path = url.replace(/#.*/, ""),
- route = routes.recognize(path);
+ const path = url.replace(/#.*/, ""),
+ route = routes.recognize(path);
if (!route) return false;
currentRoute.run("unload", null, route === currentRoute);
- var state = OSM.parseHash(url);
+ const state = OSM.parseHash(url);
map.setState(state);
window.history.pushState(state, document.title, url);
currentPath = path;
};
router.updateHash = function () {
- var hash = OSM.formatHash(map);
+ const hash = OSM.formatHash(map);
if (hash === currentHash) return;
currentHash = hash;
router.stateChange(OSM.parseHash(hash));
};
router.hashUpdated = function () {
- var hash = location.hash;
+ const hash = location.hash;
if (hash === currentHash) return;
currentHash = hash;
- var state = OSM.parseHash(hash);
+ const state = OSM.parseHash(hash);
map.setState(state);
router.stateChange(state, hash);
};
};
router.load = function () {
- var loadState = currentRoute.run("load", currentPath);
+ const loadState = currentRoute.run("load", currentPath);
router.stateChange(loadState || {});
};
}());
$(document).ready(function () {
- var defaultHomeZoom = 12;
- var map, marker, deleted_lat, deleted_lon;
+ const defaultHomeZoom = 12;
+ let map, marker, deleted_lat, deleted_lon;
if ($("#map").length) {
map = L.map("map", {
zoomControl: false
}).addLayer(new L.OSM.Mapnik());
- var position = $("html").attr("dir") === "rtl" ? "topleft" : "topright";
+ const position = $("html").attr("dir") === "rtl" ? "topleft" : "topright";
L.OSM.zoom({ position: position })
.addTo(map);
- var locate = L.control.locate({
+ const locate = L.control.locate({
position: position,
icon: "icon geolocate",
iconLoading: "icon geolocate",
}
}).addTo(map);
- var locateContainer = locate.getContainer();
+ const locateContainer = locate.getContainer();
$(locateContainer)
.removeClass("leaflet-control-locate leaflet-bar")
deleted_lon = null;
respondToHomeUpdate();
}).on("moveend", function () {
- var lat = $("#home_lat").val().trim(),
- lon = $("#home_lon").val().trim(),
- location;
+ const lat = $("#home_lat").val().trim(),
+ lon = $("#home_lon").val().trim();
+ let location;
try {
if (lat && lon) {
});
$("#home_show").click(function () {
- var lat = $("#home_lat").val(),
- lon = $("#home_lon").val();
+ const lat = $("#home_lat").val(),
+ lon = $("#home_lon").val();
map.setView([lat, lon], defaultHomeZoom);
});
$("#home_delete").click(function () {
- var lat = $("#home_lat").val(),
- lon = $("#home_lon").val();
+ const lat = $("#home_lat").val(),
+ lon = $("#home_lon").val();
$("#home_lat, #home_lon").val("");
deleted_lat = lat;
});
} else {
$("[data-user]").each(function () {
- var user = $(this).data("user");
+ const user = $(this).data("user");
if (user.lon && user.lat) {
L.marker([user.lat, user.lon], { icon: OSM.getUserIcon(user.icon) }).addTo(map)
.bindPopup(user.description, { minWidth: 200 });
}
function respondToHomeUpdate() {
- var lat = $("#home_lat").val().trim(),
- lon = $("#home_lon").val().trim(),
- location;
+ const lat = $("#home_lat").val().trim(),
+ lon = $("#home_lon").val().trim();
+ let location;
try {
if (lat && lon) {
}
function isCloseEnoughToMapCenter(location) {
- var inputPt = map.latLngToContainerPoint(location),
- centerPt = map.latLngToContainerPoint(map.getCenter());
+ const inputPt = map.latLngToContainerPoint(location),
+ centerPt = map.latLngToContainerPoint(map.getCenter());
return centerPt.distanceTo(inputPt) < 10;
}
function updateAuthUID() {
- var provider = $("select#user_auth_provider").val();
+ const provider = $("select#user_auth_provider").val();
if (provider === "openid") {
$("input#user_auth_uid").show().prop("disabled", false);
});
$("input[name=legale]").change(function () {
- var url = $(this).data("url");
+ const url = $(this).data("url");
$("#contributorTerms").html("<div class='spinner-border' role='status'><span class='visually-hidden'>" + I18n.t("browse.start_rjs.loading") + "</span></div>");
$("#contributorTerms").load(url);
$(document).ready(function () {
- var params = OSM.params();
+ const params = OSM.params();
if (params.lat && params.lon) {
- var url = "/edit";
+ let url = "/edit";
if (params.editor) url += "?editor=" + params.editor;
if (!params.zoom) params.zoom = 17;
module Api
class ChangesetCommentsController < ApiController
- before_action :check_api_writable
- before_action :authorize
+ include QueryMethods
+
+ before_action :check_api_writable, :except => [:index]
+ before_action :authorize, :except => [:index]
authorize_resource
before_action :set_request_formats
+ ##
+ # show all comments or search for a subset
+ def index
+ @comments = ChangesetComment.includes(:author).where(:visible => true).order("created_at DESC")
+ @comments = query_conditions_time(@comments)
+ @comments = query_conditions_user(@comments, :author)
+ @comments = query_limit(@comments)
+ end
+
##
# Add a comment to a changeset
def create
module Api
class ChangesetsController < ApiController
+ include QueryMethods
+
before_action :check_api_writable, :only => [:create, :update, :upload, :subscribe, :unsubscribe]
before_action :setup_user_auth, :only => [:show]
before_action :authorize, :only => [:create, :update, :upload, :close, :subscribe, :unsubscribe]
changesets = conditions_bbox(changesets, bbox)
changesets = conditions_user(changesets, params["user"], params["display_name"])
changesets = conditions_time(changesets, params["time"])
- changesets = conditions_from_to(changesets, params["from"], params["to"])
+ changesets = query_conditions_time(changesets)
changesets = conditions_open(changesets, params["open"])
changesets = conditions_closed(changesets, params["closed"])
changesets = conditions_ids(changesets, params["changesets"])
end
# limit the result
- changesets = changesets.limit(result_limit)
+ changesets = query_limit(changesets)
# preload users, tags and comments, and render result
@changesets = changesets.preload(:user, :changeset_tags, :comments)
raise OSM::APIBadUserInput, e.message.to_s
end
- ##
- # restrict changesets to those opened during a particular time period
- # works similar to from..to of notes controller, including the requirement of 'from' when specifying 'to'
- def conditions_from_to(changesets, from, to)
- if from
- begin
- from = Time.parse(from).utc
- rescue ArgumentError
- raise OSM::APIBadUserInput, "Date #{from} is in a wrong format"
- end
-
- begin
- to = if to
- Time.parse(to).utc
- else
- Time.now.utc
- end
- rescue ArgumentError
- raise OSM::APIBadUserInput, "Date #{to} is in a wrong format"
- end
-
- changesets.where(:created_at => from..to)
- else
- changesets
- end
- end
-
##
# return changesets which are open (haven't been closed yet)
# we do this by seeing if the 'closed at' time is in the future. Also if we've
changesets.where(:id => ids)
end
end
-
- ##
- # Get the maximum number of results to return
- def result_limit
- if params[:limit]
- if params[:limit].to_i.positive? && params[:limit].to_i <= Settings.max_changeset_query_limit
- params[:limit].to_i
- else
- raise OSM::APIBadUserInput, "Changeset limit must be between 1 and #{Settings.max_changeset_query_limit}"
- end
- else
- Settings.default_changeset_query_limit
- end
- end
end
end
module Api
class NotesController < ApiController
+ include QueryMethods
+
before_action :check_api_writable, :only => [:create, :comment, :close, :reopen, :destroy]
before_action :setup_user_auth, :only => [:create, :show]
before_action :authorize, :only => [:close, :reopen, :destroy, :comment]
@max_lat = bbox.max_lat
# Find the notes we want to return
- @notes = notes.bbox(bbox).order("updated_at DESC").limit(result_limit).preload(:comments)
+ notes = notes.bbox(bbox).order("updated_at DESC")
+ notes = query_limit(notes)
+ @notes = notes.preload(:comments)
# Render the result
respond_to do |format|
# Find the comments we want to return
@comments = NoteComment.where(:note => notes)
- .order(:created_at => :desc).limit(result_limit)
- .preload(:author, :note => { :comments => :author })
+ .order(:created_at => :desc)
+ @comments = query_limit(@comments)
+ @comments = @comments.preload(:author, :note => { :comments => :author })
# Render the result
respond_to do |format|
@notes = bbox_condition(@notes)
# Add any user filter
- if params[:display_name] || params[:user]
- if params[:display_name]
- @user = User.find_by(:display_name => params[:display_name])
-
- raise OSM::APIBadUserInput, "User #{params[:display_name]} not known" unless @user
- else
- @user = User.find_by(:id => params[:user])
-
- raise OSM::APIBadUserInput, "User #{params[:user]} not known" unless @user
- end
-
- @notes = @notes.joins(:comments).where(:note_comments => { :author_id => @user })
- end
+ user = query_conditions_user_value
+ @notes = @notes.joins(:comments).where(:note_comments => { :author_id => user }) if user
# Add any text filter
if params[:q]
end
# Add any date filter
- if params[:from]
- begin
- from = Time.parse(params[:from]).utc
- rescue ArgumentError
- raise OSM::APIBadUserInput, "Date #{params[:from]} is in a wrong format"
- end
-
- begin
- to = if params[:to]
- Time.parse(params[:to]).utc
- else
- Time.now.utc
- end
- rescue ArgumentError
- raise OSM::APIBadUserInput, "Date #{params[:to]} is in a wrong format"
- end
-
- @notes = if params[:sort] == "updated_at"
- @notes.where(:updated_at => from..to)
- else
- @notes.where(:created_at => from..to)
- end
- end
+ time_filter_property = if params[:sort] == "updated_at"
+ :updated_at
+ else
+ :created_at
+ end
+ @notes = query_conditions_time(@notes, time_filter_property)
# Choose the sort order
@notes = if params[:sort] == "created_at"
end
# Find the notes we want to return
- @notes = @notes.distinct.limit(result_limit).preload(:comments)
+ @notes = query_limit(@notes.distinct)
+ @notes = @notes.preload(:comments)
# Render the result
respond_to do |format|
# utility functions below.
#------------------------------------------------------------
- ##
- # Get the maximum number of results to return
- def result_limit
- if params[:limit]
- if params[:limit].to_i.positive? && params[:limit].to_i <= Settings.max_note_query_limit
- params[:limit].to_i
- else
- raise OSM::APIBadUserInput, "Note limit must be between 1 and #{Settings.max_note_query_limit}"
- end
- else
- Settings.default_note_query_limit
- end
- end
-
##
# Generate a condition to choose which notes we want based
# on their status and the user's request parameters
module ChangesetComments
class FeedsController < ApplicationController
+ include QueryMethods
+
before_action :authorize_web
before_action :set_locale
changeset = Changeset.find(changeset_id)
# Return comments for this changeset only
- @comments = changeset.comments.includes(:author, :changeset).reverse_order.limit(comments_limit)
+ @comments = changeset.comments.includes(:author, :changeset).reverse_order
+ @comments = query_limit(@comments)
else
# Return comments
- @comments = ChangesetComment.includes(:author, :changeset).where(:visible => true).order("created_at DESC").limit(comments_limit).preload(:changeset)
+ @comments = ChangesetComment.includes(:author, :changeset).where(:visible => true).order("created_at DESC")
+ @comments = query_limit(@comments)
+ @comments = @comments.preload(:changeset)
end
# Render the result
rescue OSM::APIBadUserInput
head :bad_request
end
-
- private
-
- ##
- # Get the maximum number of comments to return
- def comments_limit
- if params[:limit]
- if params[:limit].to_i.positive? && params[:limit].to_i <= 10000
- params[:limit].to_i
- else
- raise OSM::APIBadUserInput, "Comments limit must be between 1 and 10000"
- end
- else
- 100
- end
- end
end
end
--- /dev/null
+module QueryMethods
+ extend ActiveSupport::Concern
+
+ private
+
+ ##
+ # Filter the resulting items by user
+ def query_conditions_user(items, filter_property)
+ user = query_conditions_user_value
+ items = items.where(filter_property => user) if user
+ items
+ end
+
+ ##
+ # Get user value for query filtering by user
+ # Raises OSM::APIBadUserInput if user not found like notes api does, changesets api raises OSM::APINotFoundError instead
+ def query_conditions_user_value
+ if params[:display_name] || params[:user]
+ if params[:display_name]
+ user = User.find_by(:display_name => params[:display_name])
+
+ raise OSM::APIBadUserInput, "User #{params[:display_name]} not known" unless user
+ else
+ user = User.find_by(:id => params[:user])
+
+ raise OSM::APIBadUserInput, "User #{params[:user]} not known" unless user
+ end
+
+ user
+ end
+ end
+
+ ##
+ # Restrict the resulting items to those created during a particular time period
+ # Using 'to' requires specifying 'from' as well for historical reasons
+ def query_conditions_time(items, filter_property = :created_at)
+ interval = query_conditions_time_value
+
+ if interval
+ items.where(filter_property => interval)
+ else
+ items
+ end
+ end
+
+ ##
+ # Get query time interval from request parameters or nil
+ def query_conditions_time_value
+ if params[:from]
+ begin
+ from = Time.parse(params[:from]).utc
+ rescue ArgumentError
+ raise OSM::APIBadUserInput, "Date #{params[:from]} is in a wrong format"
+ end
+
+ begin
+ to = if params[:to]
+ Time.parse(params[:to]).utc
+ else
+ Time.now.utc
+ end
+ rescue ArgumentError
+ raise OSM::APIBadUserInput, "Date #{params[:to]} is in a wrong format"
+ end
+
+ from..to
+ end
+ end
+
+ ##
+ # Limit the result according to request parameters and settings
+ def query_limit(items)
+ items.limit(query_limit_value)
+ end
+
+ ##
+ # Get query limit value from request parameters and settings
+ def query_limit_value
+ name = controller_path.sub(%r{^api/}, "").tr("/", "_").singularize
+ max_limit = Settings["max_#{name}_query_limit"]
+ default_limit = Settings["default_#{name}_query_limit"]
+ if params[:limit]
+ if params[:limit].to_i.positive? && params[:limit].to_i <= max_limit
+ params[:limit].to_i
+ else
+ raise OSM::APIBadUserInput, "#{controller_name.classify} limit must be between 1 and #{max_limit}"
+ end
+ else
+ default_limit
+ end
+ end
+end
trace_score = traces.size * 50
diary_entry_score = diary_entries.visible.inject(0) { |acc, elem| acc + elem.body.spam_score }
diary_comment_score = diary_comments.visible.inject(0) { |acc, elem| acc + elem.body.spam_score }
+ report_score = Report.where(:category => "spam", :issue => issues.with_status("open")).count * 20
score = description.spam_score / 4.0
score += diary_entries.visible.where("created_at > ?", 1.day.ago).count * 10
score += diary_entry_score / diary_entries.visible.length unless diary_entries.visible.empty?
score += diary_comment_score / diary_comments.visible.length unless diary_comments.visible.empty?
+ score += report_score
score -= changeset_score
score -= trace_score
--- /dev/null
+json.id changeset_comment.id
+json.visible changeset_comment.visible
+json.date changeset_comment.created_at.xmlschema
+if changeset_comment.author.data_public?
+ json.uid changeset_comment.author.id
+ json.user changeset_comment.author.display_name
+end
+json.text changeset_comment.body
--- /dev/null
+cattrs = {
+ "id" => changeset_comment.id,
+ "date" => changeset_comment.created_at.xmlschema,
+ "visible" => changeset_comment.visible
+}
+if changeset_comment.author.data_public?
+ cattrs["uid"] = changeset_comment.author.id
+ cattrs["user"] = changeset_comment.author.display_name
+end
+xml.comment(cattrs) do |comment_xml_node|
+ comment_xml_node.text(changeset_comment.body)
+end
--- /dev/null
+json.partial! "api/root_attributes"
+
+json.comments(@comments) do |comment|
+ json.partial! comment
+end
--- /dev/null
+xml.instruct! :xml, :version => "1.0"
+
+xml.osm(OSM::API.new.xml_root_attributes) do |osm|
+ @comments.includes(:author).each do |comment|
+ osm << render(comment)
+ end
+end
if @comments
json.comments(@comments) do |comment|
- json.id comment.id
- json.visible comment.visible
- json.date comment.created_at.xmlschema
- if comment.author.data_public?
- json.uid comment.author.id
- json.user comment.author.display_name
- end
- json.text comment.body
+ json.partial! comment
end
end
if @comments
changeset_xml_node.discussion do |discussion_xml_node|
@comments.each do |comment|
- cattrs = {
- "id" => comment.id,
- "date" => comment.created_at.xmlschema,
- "visible" => comment.visible
- }
- if comment.author.data_public?
- cattrs["uid"] = comment.author.id
- cattrs["user"] = comment.author.display_name
- end
- discussion_xml_node.comment(cattrs) do |comment_xml_node|
- comment_xml_node.text(comment.body)
- end
+ discussion_xml_node << render(comment)
end
end
end
"no-useless-call": "error",
"no-useless-concat": "error",
"no-useless-return": "error",
+ "no-var": "error",
"no-void": "error",
"no-warning-comments": "warn",
"operator-assignment": "error",
+ "prefer-const": "error",
"prefer-object-spread": "error",
"radix": ["error", "always"],
"yoda": "error"
end
namespace :api, :path => "api/0.6" do
+ resources :changeset_comments, :only => :index
+
resources :nodes, :only => [:index, :create]
resources :nodes, :path => "node", :id => /\d+/, :only => [:show, :update, :destroy] do
scope :module => :nodes do
default_changeset_query_limit: 100
# Maximum limit on the number of changesets returned by the changeset query api method
max_changeset_query_limit: 100
+# Default limit on the number of changeset comments returned by the api
+default_changeset_comment_query_limit: 100
+# Maximum limit on the number of changesets comments returned by the api
+max_changeset_comment_query_limit: 10000
+# Default limit on the number of changeset comments in feeds
+default_changeset_comments_feed_query_limit: 100
+# Maximum limit on the number of changesets comments in feeds
+max_changeset_comments_feed_query_limit: 10000
# Maximum number of nodes that will be returned by the api in a map request
max_number_of_nodes: 50000
# Maximum number of nodes that can be in a way (checked on save)
cK1+/2V+OkM/0nXjxPwPj7LiOediUyZNUn48r29uGOL1S83PSUdyST207CP6mZjc
K8aJmnGsVEAcWPzbpNh14q/c
-----END PRIVATE KEY-----
+# Run system tests using headless Firefox
+system_test_headless: true
# Override Firefox binary used in system tests
#system_test_firefox_binary:
end
class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
- driven_by :selenium, :using => :headless_firefox do |options|
+ driven_by :selenium, :using => Settings.system_test_headless ? :headless_firefox : :firefox do |options|
options.add_preference("intl.accept_languages", "en")
options.binary = Settings.system_test_firefox_binary if Settings.system_test_firefox_binary
end
##
# test all routes which lead to this controller
def test_routes
+ assert_routing(
+ { :path => "/api/0.6/changeset_comments", :method => :get },
+ { :controller => "api/changeset_comments", :action => "index" }
+ )
+ assert_routing(
+ { :path => "/api/0.6/changeset_comments.json", :method => :get },
+ { :controller => "api/changeset_comments", :action => "index", :format => "json" }
+ )
assert_routing(
{ :path => "/api/0.6/changeset/1/comment", :method => :post },
{ :controller => "api/changeset_comments", :action => "create", :id => "1" }
)
end
+ def test_index
+ user1 = create(:user)
+ user2 = create(:user)
+ changeset1 = create(:changeset, :closed, :user => user2)
+ comment11 = create(:changeset_comment, :changeset => changeset1, :author => user1, :created_at => "2023-01-01", :body => "changeset 1 question")
+ comment12 = create(:changeset_comment, :changeset => changeset1, :author => user2, :created_at => "2023-02-01", :body => "changeset 1 answer")
+ changeset2 = create(:changeset, :closed, :user => user1)
+ comment21 = create(:changeset_comment, :changeset => changeset2, :author => user1, :created_at => "2023-03-01", :body => "changeset 2 note")
+ comment22 = create(:changeset_comment, :changeset => changeset2, :author => user1, :created_at => "2023-04-01", :body => "changeset 2 extra note")
+ comment23 = create(:changeset_comment, :changeset => changeset2, :author => user2, :created_at => "2023-05-01", :body => "changeset 2 review")
+
+ get api_changeset_comments_path
+ assert_response :success
+ assert_comments_in_order [comment23, comment22, comment21, comment12, comment11]
+
+ get api_changeset_comments_path(:limit => 3)
+ assert_response :success
+ assert_comments_in_order [comment23, comment22, comment21]
+
+ get api_changeset_comments_path(:from => "2023-03-15T00:00:00Z")
+ assert_response :success
+ assert_comments_in_order [comment23, comment22]
+
+ get api_changeset_comments_path(:from => "2023-01-15T00:00:00Z", :to => "2023-04-15T00:00:00Z")
+ assert_response :success
+ assert_comments_in_order [comment22, comment21, comment12]
+
+ get api_changeset_comments_path(:user => user1.id)
+ assert_response :success
+ assert_comments_in_order [comment22, comment21, comment11]
+
+ get api_changeset_comments_path(:from => "2023-03-15T00:00:00Z", :format => "json")
+ assert_response :success
+ js = ActiveSupport::JSON.decode(@response.body)
+ assert_not_nil js
+ assert_equal 2, js["comments"].count
+ assert_equal comment23.id, js["comments"][0]["id"]
+ assert_equal comment22.id, js["comments"][1]["id"]
+ end
+
def test_create_by_unauthorized
assert_no_difference "ChangesetComment.count" do
post changeset_comment_path(create(:changeset, :closed), :text => "This is a comment")
assert_response :success
assert comment.reload.visible
end
+
+ private
+
+ ##
+ # check that certain comments exist in the output in the specified order
+ def assert_comments_in_order(comments)
+ assert_dom "osm > comment", comments.size do |dom_comments|
+ comments.zip(dom_comments).each do |comment, dom_comment|
+ assert_dom dom_comment, "> @id", comment.id.to_s
+ assert_dom dom_comment, "> @uid", comment.author.id.to_s
+ assert_dom dom_comment, "> @user", comment.author.display_name
+ assert_dom dom_comment, "> text", comment.body
+ end
+ end
+ end
end
end
assert_select "gpx", :count => 1 do
assert_select "wpt", :count => 1
end
+
+ user2 = create(:user)
+ get search_api_notes_path(:user => user2.id, :format => "xml")
+ assert_response :success
+ assert_equal "application/xml", @response.media_type
+ assert_select "osm", :count => 1 do
+ assert_select "note", :count => 0
+ end
+ end
+
+ def test_search_by_time_success
+ note1 = create(:note, :created_at => "2020-02-01T00:00:00Z", :updated_at => "2020-04-01T00:00:00Z")
+ note2 = create(:note, :created_at => "2020-03-01T00:00:00Z", :updated_at => "2020-05-01T00:00:00Z")
+
+ get search_api_notes_path(:from => "2020-02-15T00:00:00Z", :to => "2020-04-15T00:00:00Z", :format => "xml")
+ assert_response :success
+ assert_equal "application/xml", @response.media_type
+ assert_select "osm", :count => 1 do
+ assert_select "note", :count => 1 do
+ assert_select "id", note2.id.to_s
+ end
+ end
+
+ get search_api_notes_path(:from => "2020-02-15T00:00:00Z", :to => "2020-04-15T00:00:00Z", :sort => "updated_at", :format => "xml")
+ assert_response :success
+ assert_equal "application/xml", @response.media_type
+ assert_select "osm", :count => 1 do
+ assert_select "note", :count => 1 do
+ assert_select "id", note1.id.to_s
+ end
+ end
end
def test_search_by_bbox_success
describe(".params", function () {
it("parses params", function () {
- var params = OSM.params("?foo=a&bar=b");
+ const params = OSM.params("?foo=a&bar=b");
expect(params).to.have.property("foo", "a");
expect(params).to.have.property("bar", "b");
});
});
it("parses marker params", function () {
- var params = OSM.mapParams("?mlat=57.6247&mlon=-3.6845");
+ const params = OSM.mapParams("?mlat=57.6247&mlon=-3.6845");
expect(params).to.have.property("mlat", 57.6247);
expect(params).to.have.property("mlon", -3.6845);
expect(params).to.have.property("marker", true);
});
it("parses object params", function () {
- var params = OSM.mapParams("?node=1");
+ let params = OSM.mapParams("?node=1");
expect(params).to.have.property("object");
expect(params.object).to.eql({ type: "node", id: 1 });
});
it("parses bbox params", function () {
- var expected = L.latLngBounds([57.6247, -3.6845], [57.7247, -3.7845]);
- var params = OSM.mapParams("?bbox=-3.6845,57.6247,-3.7845,57.7247");
+ const expected = L.latLngBounds([57.6247, -3.6845], [57.7247, -3.7845]);
+ let params = OSM.mapParams("?bbox=-3.6845,57.6247,-3.7845,57.7247");
expect(params).to.have.property("bounds").deep.equal(expected);
params = OSM.mapParams("?minlon=-3.6845&minlat=57.6247&maxlon=-3.7845&maxlat=57.7247");
});
it("parses mlat/mlon/zoom params", function () {
- var params = OSM.mapParams("?mlat=57.6247&mlon=-3.6845");
+ let params = OSM.mapParams("?mlat=57.6247&mlon=-3.6845");
expect(params).to.have.property("lat", 57.6247);
expect(params).to.have.property("lon", -3.6845);
expect(params).to.have.property("zoom", 12);
it("sets lat/lon from OSM.home", function () {
OSM.home = { lat: 57.6247, lon: -3.6845 };
- var params = OSM.mapParams("?");
+ const params = OSM.mapParams("?");
expect(params).to.have.property("lat", 57.6247);
expect(params).to.have.property("lon", -3.6845);
});
it("sets bbox from OSM.location", function () {
OSM.location = { minlon: -3.6845, minlat: 57.6247, maxlon: -3.7845, maxlat: 57.7247 };
- var expected = L.latLngBounds([57.6247, -3.6845], [57.7247, -3.7845]);
- var params = OSM.mapParams("?");
+ const expected = L.latLngBounds([57.6247, -3.6845], [57.7247, -3.7845]);
+ const params = OSM.mapParams("?");
expect(params).to.have.property("bounds").deep.equal(expected);
});
it("parses params from the _osm_location cookie", function () {
document.cookie = "_osm_location=-3.6845|57.6247|5|M";
- var params = OSM.mapParams("?");
+ const params = OSM.mapParams("?");
expect(params).to.have.property("lat", 57.6247);
expect(params).to.have.property("lon", -3.6845);
expect(params).to.have.property("zoom", 5);
});
it("defaults lat/lon to London", function () {
- var params = OSM.mapParams("?");
+ let params = OSM.mapParams("?");
expect(params).to.have.property("lat", 51.5);
expect(params).to.have.property("lon", -0.1);
expect(params).to.have.property("zoom", 5);
});
it("parses layers param", function () {
- var params = OSM.mapParams("?");
+ let params = OSM.mapParams("?");
expect(params).to.have.property("layers", "");
document.cookie = "_osm_location=-3.6845|57.6247|5|C";
describe(".parseHash", function () {
it("parses lat/lon/zoom params", function () {
- var args = OSM.parseHash("#map=5/57.6247/-3.6845&layers=M");
+ const args = OSM.parseHash("#map=5/57.6247/-3.6845&layers=M");
expect(args).to.have.property("center").deep.equal(L.latLng(57.6247, -3.6845));
expect(args).to.have.property("zoom", 5);
});
it("parses layers params", function () {
- var args = OSM.parseHash("#map=5/57.6247/-3.6845&layers=M");
+ const args = OSM.parseHash("#map=5/57.6247/-3.6845&layers=M");
expect(args).to.have.property("layers", "M");
});
});
describe(".formatHash", function () {
it("formats lat/lon/zoom params", function () {
- var args = { center: L.latLng(57.6247, -3.6845), zoom: 9 };
+ const args = { center: L.latLng(57.6247, -3.6845), zoom: 9 };
expect(OSM.formatHash(args)).to.eq("#map=9/57.625/-3.685");
});
it("respects zoomPrecision", function () {
- var args = { center: L.latLng(57.6247, -3.6845), zoom: 5 };
+ let args = { center: L.latLng(57.6247, -3.6845), zoom: 5 };
expect(OSM.formatHash(args)).to.eq("#map=5/57.62/-3.68");
});
it("formats layers params", function () {
- var args = { center: L.latLng(57.6247, -3.6845), zoom: 9, layers: "C" };
+ const args = { center: L.latLng(57.6247, -3.6845), zoom: 9, layers: "C" };
expect(OSM.formatHash(args)).to.eq("#map=9/57.625/-3.685&layers=C");
});
it("ignores default layers", function () {
- var args = { center: L.latLng(57.6247, -3.6845), zoom: 9, layers: "M" };
+ const args = { center: L.latLng(57.6247, -3.6845), zoom: 9, layers: "M" };
expect(OSM.formatHash(args)).to.eq("#map=9/57.625/-3.685");
});
});
describe(".locationCookie", function () {
it("creates a location cookie value", function () {
$("body").html($("<div id='map'>"));
- var map = new L.OSM.Map("map", { center: [57.6247, -3.6845], zoom: 9 });
+ const map = new L.OSM.Map("map", { center: [57.6247, -3.6845], zoom: 9 });
map.updateLayers("");
expect(OSM.locationCookie(map)).to.eq("-3.685|57.625|9|M");
});
it("respects zoomPrecision", function () {
$("body").html($("<div id='map'>"));
- var map = new L.OSM.Map("map", { center: [57.6247, -3.6845], zoom: 9 });
+ const map = new L.OSM.Map("map", { center: [57.6247, -3.6845], zoom: 9 });
map.updateLayers("");
expect(OSM.locationCookie(map)).to.eq("-3.685|57.625|9|M");
// map.setZoom() doesn't update the zoom level for some reason
describe(".distance", function () {
it("computes distance between points", function () {
- var latlng1 = L.latLng(51.76712, -0.00484),
- latlng2 = L.latLng(51.7675159, -0.0078329);
+ const latlng1 = L.latLng(51.76712, -0.00484),
+ latlng2 = L.latLng(51.7675159, -0.0078329);
expect(OSM.distance(latlng1, latlng2)).to.be.closeTo(210.664, 0.005);
expect(OSM.distance(latlng2, latlng1)).to.be.closeTo(210.664, 0.005);
# Capybara Webkit: https://github.com/jejacks0n/teaspoon/wiki/Using-Capybara-Webkit
require "selenium-webdriver"
config.driver = :selenium
- firefox_options = Selenium::WebDriver::Firefox::Options.new(:args => ["-headless"])
+ firefox_options = Selenium::WebDriver::Firefox::Options.new
+ firefox_options.args = ["-headless"] if Settings.system_test_headless
firefox_options.binary = Settings.system_test_firefox_binary if Settings.system_test_firefox_binary
config.driver_options = {
:client_driver => :firefox,