--- /dev/null
+OSM.DirectionsRouteOutput = function (map) {
+ const popup = L.popup({ autoPanPadding: [100, 100] });
+
+ const polyline = L.polyline([], {
+ color: "#03f",
+ opacity: 0.3,
+ weight: 10
+ });
+
+ const highlight = L.polyline([], {
+ color: "#ff0",
+ opacity: 0.5,
+ weight: 12
+ });
+
+ let downloadURL = null;
+
+ function formatTotalDistance(m) {
+ if (m < 1000) {
+ return OSM.i18n.t("javascripts.directions.distance_m", { distance: Math.round(m) });
+ } else if (m < 10000) {
+ return OSM.i18n.t("javascripts.directions.distance_km", { distance: (m / 1000.0).toFixed(1) });
+ } else {
+ return OSM.i18n.t("javascripts.directions.distance_km", { distance: Math.round(m / 1000) });
+ }
+ }
+
+ function formatStepDistance(m) {
+ if (m < 5) {
+ return "";
+ } else if (m < 200) {
+ return OSM.i18n.t("javascripts.directions.distance_m", { distance: String(Math.round(m / 10) * 10) });
+ } else if (m < 1500) {
+ return OSM.i18n.t("javascripts.directions.distance_m", { distance: String(Math.round(m / 100) * 100) });
+ } else if (m < 5000) {
+ return OSM.i18n.t("javascripts.directions.distance_km", { distance: String(Math.round(m / 100) / 10) });
+ } else {
+ return OSM.i18n.t("javascripts.directions.distance_km", { distance: String(Math.round(m / 1000)) });
+ }
+ }
+
+ function formatHeight(m) {
+ return OSM.i18n.t("javascripts.directions.distance_m", { distance: Math.round(m) });
+ }
+
+ function formatTime(s) {
+ let m = Math.round(s / 60);
+ const h = Math.floor(m / 60);
+ m -= h * 60;
+ return h + ":" + (m < 10 ? "0" : "") + m;
+ }
+
+ const routeOutput = {};
+
+ routeOutput.write = function (content, route) {
+ polyline
+ .setLatLngs(route.line)
+ .addTo(map);
+
+ const distanceText = $("<p>").append(
+ OSM.i18n.t("javascripts.directions.distance") + ": " + formatTotalDistance(route.distance) + ". " +
+ OSM.i18n.t("javascripts.directions.time") + ": " + formatTime(route.time) + ".");
+ if (typeof route.ascend !== "undefined" && typeof route.descend !== "undefined") {
+ distanceText.append(
+ $("<br>"),
+ OSM.i18n.t("javascripts.directions.ascend") + ": " + formatHeight(route.ascend) + ". " +
+ OSM.i18n.t("javascripts.directions.descend") + ": " + formatHeight(route.descend) + ".");
+ }
+
+ const turnByTurnTable = $("<table class='table table-hover table-sm mb-3'>")
+ .append($("<tbody>"));
+
+ content
+ .empty()
+ .append(
+ distanceText,
+ turnByTurnTable
+ );
+
+ for (const [i, [direction, instruction, dist, lineseg]] of route.steps.entries()) {
+ const row = $("<tr class='turn'/>").appendTo(turnByTurnTable);
+
+ if (direction) {
+ row.append("<td class='border-0'><svg width='20' height='20' class='d-block'><use href='#routing-sprite-" + direction + "' /></svg></td>");
+ } else {
+ row.append("<td class='border-0'>");
+ }
+ row.append(`<td><b>${i + 1}.</b> ${instruction}`);
+ row.append("<td class='distance text-body-secondary text-end'>" + formatStepDistance(dist));
+
+ row.on("click", function () {
+ popup
+ .setLatLng(lineseg[0])
+ .setContent(`<p><b>${i + 1}.</b> ${instruction}</p>`)
+ .openOn(map);
+ });
+
+ row
+ .on("mouseenter", function () {
+ highlight
+ .setLatLngs(lineseg)
+ .addTo(map);
+ })
+ .on("mouseleave", function () {
+ map.removeLayer(highlight);
+ });
+ }
+
+ const blob = new Blob([JSON.stringify(polyline.toGeoJSON())], { type: "application/json" });
+ URL.revokeObjectURL(downloadURL);
+ downloadURL = URL.createObjectURL(blob);
+
+ content.append(`<p class="text-center"><a href="${downloadURL}" download="${
+ OSM.i18n.t("javascripts.directions.filename")
+ }">${
+ OSM.i18n.t("javascripts.directions.download")
+ }</a></p>`);
+
+ content.append("<p class=\"text-center\">" +
+ OSM.i18n.t("javascripts.directions.instructions.courtesy", {
+ link: `<a href="${route.creditlink}" target="_blank">${route.credit}</a>`
+ }) +
+ "</p>");
+ };
+
+ routeOutput.fit = function () {
+ map.fitBounds(polyline.getBounds().pad(0.05));
+ };
+
+ routeOutput.isVisible = function () {
+ return map.hasLayer(polyline);
+ };
+
+ routeOutput.remove = function (content) {
+ content.empty();
+ map
+ .removeLayer(popup)
+ .removeLayer(polyline);
+ };
+
+ return routeOutput;
+};
//= require ./directions-endpoint
+//= require ./directions-route-output
//= require_self
//= require_tree ./directions
let lastLocation = [];
let chosenEngine;
- const popup = L.popup({ autoPanPadding: [100, 100] });
-
- const polyline = L.polyline([], {
- color: "#03f",
- opacity: 0.3,
- weight: 10
- });
-
- const highlight = L.polyline([], {
- color: "#ff0",
- opacity: 0.5,
- weight: 12
- });
+ const routeOutput = OSM.DirectionsRouteOutput(map);
const endpointDragCallback = function (dragging) {
- if (!map.hasLayer(polyline)) return;
+ if (!routeOutput.isVisible()) return;
if (dragging && !chosenEngine.draggable) return;
if (dragging && controller) return;
OSM.DirectionsEndpoint(map, $("input[name='route_to']"), { icon: "MARKER_RED" }, endpointDragCallback, endpointChangeCallback)
];
- let downloadURL = null;
-
const expiry = new Date();
expiry.setYear(expiry.getFullYear() + 10);
OSM.router.route("/" + OSM.formatHash(map));
});
- function formatTotalDistance(m) {
- if (m < 1000) {
- return OSM.i18n.t("javascripts.directions.distance_m", { distance: Math.round(m) });
- } else if (m < 10000) {
- return OSM.i18n.t("javascripts.directions.distance_km", { distance: (m / 1000.0).toFixed(1) });
- } else {
- return OSM.i18n.t("javascripts.directions.distance_km", { distance: Math.round(m / 1000) });
- }
- }
-
- function formatStepDistance(m) {
- if (m < 5) {
- return "";
- } else if (m < 200) {
- return OSM.i18n.t("javascripts.directions.distance_m", { distance: String(Math.round(m / 10) * 10) });
- } else if (m < 1500) {
- return OSM.i18n.t("javascripts.directions.distance_m", { distance: String(Math.round(m / 100) * 100) });
- } else if (m < 5000) {
- return OSM.i18n.t("javascripts.directions.distance_km", { distance: String(Math.round(m / 100) / 10) });
- } else {
- return OSM.i18n.t("javascripts.directions.distance_km", { distance: String(Math.round(m / 1000)) });
- }
- }
-
- function formatHeight(m) {
- return OSM.i18n.t("javascripts.directions.distance_m", { distance: Math.round(m) });
- }
-
- function formatTime(s) {
- let m = Math.round(s / 60);
- const h = Math.floor(m / 60);
- m -= h * 60;
- return h + ":" + (m < 10 ? "0" : "") + m;
- }
-
function setEngine(id) {
const engines = OSM.Directions.engines;
const desired = engines.find(engine => engine.id === id);
map.setSidebarOverlaid(false);
controller = new AbortController();
chosenEngine.getRoute(points, controller.signal).then(function (route) {
- polyline
- .setLatLngs(route.line)
- .addTo(map);
-
+ routeOutput.write($("#directions_content"), route);
if (fitRoute) {
- map.fitBounds(polyline.getBounds().pad(0.05));
+ routeOutput.fit();
}
-
- const distanceText = $("<p>").append(
- OSM.i18n.t("javascripts.directions.distance") + ": " + formatTotalDistance(route.distance) + ". " +
- OSM.i18n.t("javascripts.directions.time") + ": " + formatTime(route.time) + ".");
- if (typeof route.ascend !== "undefined" && typeof route.descend !== "undefined") {
- distanceText.append(
- $("<br>"),
- OSM.i18n.t("javascripts.directions.ascend") + ": " + formatHeight(route.ascend) + ". " +
- OSM.i18n.t("javascripts.directions.descend") + ": " + formatHeight(route.descend) + ".");
- }
-
- const turnByTurnTable = $("<table class='table table-hover table-sm mb-3'>")
- .append($("<tbody>"));
-
- $("#directions_content")
- .empty()
- .append(
- distanceText,
- turnByTurnTable
- );
-
- // Add each row
- turnByTurnTable.append(route.steps.map(([direction, instruction, dist, lineseg], i) => {
- const row = $("<tr class='turn'/>");
- if (direction) {
- row.append("<td class='border-0'><svg width='20' height='20' class='d-block'><use href='#routing-sprite-" + direction + "' /></svg></td>");
- } else {
- row.append("<td class='border-0'>");
- }
- row.append(`<td><b>${i + 1}.</b> ${instruction}`);
- row.append("<td class='distance text-body-secondary text-end'>" + formatStepDistance(dist));
-
- row.on("click", function () {
- popup
- .setLatLng(lineseg[0])
- .setContent(`<p><b>${i + 1}.</b> ${instruction}</p>`)
- .openOn(map);
- });
-
- row.hover(function () {
- highlight
- .setLatLngs(lineseg)
- .addTo(map);
- }, function () {
- map.removeLayer(highlight);
- });
-
- return row;
- }));
-
- const blob = new Blob([JSON.stringify(polyline.toGeoJSON())], { type: "application/json" });
- URL.revokeObjectURL(downloadURL);
- downloadURL = URL.createObjectURL(blob);
-
- $("#directions_content").append(`<p class="text-center"><a href="${downloadURL}" download="${
- OSM.i18n.t("javascripts.directions.filename")
- }">${
- OSM.i18n.t("javascripts.directions.download")
- }</a></p>`);
-
- $("#directions_content").append("<p class=\"text-center\">" +
- OSM.i18n.t("javascripts.directions.instructions.courtesy", { link: chosenEngine.creditline }) +
- "</p>");
}).catch(function () {
- map.removeLayer(polyline);
+ routeOutput.remove($("#directions_content"));
if (reportErrors) {
$("#directions_content").html("<div class=\"alert alert-danger\">" + OSM.i18n.t("javascripts.directions.errors.no_route") + "</div>");
}
});
}
- function hideRoute(e) {
+ function closeButtonListener(e) {
e.stopPropagation();
- map.removeLayer(polyline);
- $("#directions_content").html("");
- popup.close();
+ routeOutput.remove($("#directions_content"));
map.setSidebarOverlaid(true);
// TODO: collapse width of sidebar back to previous
}
}
function enableListeners() {
- $("#sidebar .sidebar-close-controls button").on("click", hideRoute);
+ $("#sidebar .sidebar-close-controls button").on("click", closeButtonListener);
$("#map").on("dragend dragover", function (e) {
e.preventDefault();
$(".search_form").show();
$(".directions_form").hide();
- $("#sidebar .sidebar-close-controls button").off("click", hideRoute);
+ $("#sidebar .sidebar-close-controls button").off("click", closeButtonListener);
$("#map").off("dragend dragover drop");
map.off("locationfound", sendstartinglocation);
endpoints[0].clearValue();
endpoints[1].clearValue();
- map
- .removeLayer(popup)
- .removeLayer(polyline);
+ routeOutput.remove($("#directions_content"));
};
return page;
line: steps.flatMap(step => step[3]),
steps,
distance: leg.distance,
- time: leg.duration
+ time: leg.duration,
+ credit: "OSRM (FOSSGIS)",
+ creditlink: "https://routing.openstreetmap.de/about.html"
};
}
return {
mode: modeId,
provider: "fossgis_osrm",
- creditline: "<a href=\"https://routing.openstreetmap.de/about.html\" target=\"_blank\">OSRM (FOSSGIS)</a>",
draggable: true,
getRoute: function (points, signal) {
line,
steps,
distance: leg.summary.length * 1000,
- time: leg.summary.time
+ time: leg.summary.time,
+ credit: "Valhalla (FOSSGIS)",
+ creditlink: "https://gis-ops.com/global-open-valhalla-server-online/"
};
}
return {
mode: modeId,
provider: "fossgis_valhalla",
- creditline:
- "<a href='https://gis-ops.com/global-open-valhalla-server-online/' target='_blank'>Valhalla (FOSSGIS)</a>",
draggable: false,
getRoute: function (points, signal) {
distance: path.distance,
time: path.time / 1000,
ascend: path.ascend,
- descend: path.descend
+ descend: path.descend,
+ credit: "GraphHopper",
+ creditlink: "https://www.graphhopper.com/"
};
}
return {
mode: modeId,
provider: "graphhopper",
- creditline: "<a href=\"https://www.graphhopper.com/\" target=\"_blank\">GraphHopper</a>",
draggable: false,
getRoute: function (points, signal) {
--- /dev/null
+OSM.HistoryChangesetsLayer = L.FeatureGroup.extend({
+ _changesets: [],
+
+ updateChangesets: function (map, changesets) {
+ this._changesets = changesets;
+ this.updateChangesetShapes(map);
+ },
+
+ updateChangesetShapes: function (map) {
+ this.clearLayers();
+
+ for (const changeset of this._changesets) {
+ 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);
+ topRight.x += ((minSize - width) / 2);
+ }
+
+ if (height < minSize) {
+ bottomLeft.y += ((minSize - height) / 2);
+ topRight.y -= ((minSize - height) / 2);
+ }
+
+ changeset.bounds = L.latLngBounds(map.unproject(bottomLeft),
+ map.unproject(topRight));
+ }
+
+ this._changesets.sort(function (a, b) {
+ return b.bounds.getSize() - a.bounds.getSize();
+ });
+
+ this.updateChangesetLocations(map);
+
+ for (const changeset of this._changesets) {
+ const rect = L.rectangle(changeset.bounds,
+ { weight: 2, color: "#FF9500", opacity: 1, fillColor: "#FFFFAF", fillOpacity: 0 });
+ rect.id = changeset.id;
+ rect.addTo(this);
+ }
+ },
+
+ updateChangesetLocations: function (map) {
+ const mapCenterLng = map.getCenter().lng;
+
+ for (const changeset of this._changesets) {
+ const changesetSouthWest = changeset.bounds.getSouthWest();
+ const changesetNorthEast = changeset.bounds.getNorthEast();
+ const changesetCenterLng = (changesetSouthWest.lng + changesetNorthEast.lng) / 2;
+ const shiftInWorldCircumferences = Math.round((changesetCenterLng - mapCenterLng) / 360);
+
+ if (shiftInWorldCircumferences) {
+ changesetSouthWest.lng -= shiftInWorldCircumferences * 360;
+ changesetNorthEast.lng -= shiftInWorldCircumferences * 360;
+
+ this.getLayer(changeset.id)?.setBounds(changeset.bounds);
+ }
+ }
+ },
+
+ highlightChangeset: function (id) {
+ this.getLayer(id)?.setStyle({ fillOpacity: 0.3, color: "#FF6600", weight: 3 });
+ },
+
+ unHighlightChangeset: function (id) {
+ this.getLayer(id)?.setStyle({ fillOpacity: 0, color: "#FF9500", weight: 2 });
+ },
+
+ getLayerId: function (layer) {
+ return layer.id;
+ }
+});
//= require jquery-simulate/jquery.simulate
+//= require ./history-changesets-layer
OSM.History = function (map) {
const page = {};
unHighlightChangeset($(this).data("changeset").id);
});
- const group = L.featureGroup()
+ const changesetsLayer = new OSM.HistoryChangesetsLayer()
.on("mouseover", function (e) {
highlightChangeset(e.layer.id);
})
clickChangeset(e.layer.id, e.originalEvent);
});
- group.getLayerId = function (layer) {
- return layer.id;
- };
-
let changesetIntersectionObserver;
function disableChangesetIntersectionObserver() {
}
function highlightChangeset(id) {
- const layer = group.getLayer(id);
- if (layer) layer.setStyle({ fillOpacity: 0.3, color: "#FF6600", weight: 3 });
+ changesetsLayer.highlightChangeset(id);
$("#changeset_" + id).addClass("selected");
}
function unHighlightChangeset(id) {
- const layer = group.getLayer(id);
- if (layer) layer.setStyle({ fillOpacity: 0, color: "#FF9500", weight: 2 });
+ changesetsLayer.unHighlightChangeset(id);
$("#changeset_" + id).removeClass("selected");
}
const neClamped = crs.unproject(crs.project(ne));
if (sw.lat >= swClamped.lat || ne.lat <= neClamped.lat || ne.lng - sw.lng < 360) {
- data.set("bbox", map.getBounds().wrap().toBBoxString());
+ data.set("bbox", map.getBounds().toBBoxString());
}
}
}
}
- function reloadChangesetsBecauseOfMapMovement() {
- OSM.router.replace("/history" + window.location.hash);
- loadFirstChangesets();
- }
-
- let changesets = [];
-
- function updateBounds() {
- group.clearLayers();
-
- for (const changeset of changesets) {
- 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);
- topRight.x += ((minSize - width) / 2);
- }
-
- if (height < minSize) {
- bottomLeft.y += ((minSize - height) / 2);
- topRight.y -= ((minSize - height) / 2);
- }
-
- changeset.bounds = L.latLngBounds(map.unproject(bottomLeft),
- map.unproject(topRight));
+ function moveEndListener() {
+ if (location.pathname === "/history") {
+ OSM.router.replace("/history" + window.location.hash);
+ loadFirstChangesets();
+ } else {
+ changesetsLayer.updateChangesetsPositions(map);
}
+ }
- changesets.sort(function (a, b) {
- return b.bounds.getSize() - a.bounds.getSize();
- });
-
- for (const changeset of changesets) {
- const rect = L.rectangle(changeset.bounds,
- { weight: 2, color: "#FF9500", opacity: 1, fillColor: "#FFFFAF", fillOpacity: 0 });
- rect.id = changeset.id;
- rect.addTo(group);
- }
+ function zoomEndListener() {
+ changesetsLayer.updateChangesetShapes(map);
}
function updateMap() {
- changesets = $("[data-changeset]").map(function (index, element) {
+ const changesets = $("[data-changeset]").map(function (index, element) {
return $(element).data("changeset");
}).get().filter(function (changeset) {
return changeset.bbox;
});
- updateBounds();
+ changesetsLayer.updateChangesets(map, changesets);
if (location.pathname !== "/history") {
- const bounds = group.getBounds();
+ const bounds = changesetsLayer.getBounds();
if (bounds.isValid()) map.fitBounds(bounds);
}
}
};
page.load = function () {
- map.addLayer(group);
-
- if (location.pathname === "/history") {
- map.on("moveend", reloadChangesetsBecauseOfMapMovement);
- }
-
- map.on("zoomend", updateBounds);
-
+ map.addLayer(changesetsLayer);
+ map.on("moveend", moveEndListener);
+ map.on("zoomend", zoomEndListener);
loadFirstChangesets();
};
page.unload = function () {
- map.removeLayer(group);
- map.off("moveend", reloadChangesetsBecauseOfMapMovement);
- map.off("zoomend", updateBounds);
+ map.removeLayer(changesetsLayer);
+ map.off("moveend", moveEndListener);
+ map.off("zoomend", zoomEndListener);
disableChangesetIntersectionObserver();
};
changesets.where("false")
end
elsif @params[:bbox]
- changesets = conditions_bbox(changesets, BoundingBox.from_bbox_params(params))
+ bbox_array = @params[:bbox].split(",").map(&:to_f)
+ raise OSM::APIBadUserInput, "The parameter bbox must be of the form min_lon,min_lat,max_lon,max_lat" unless bbox_array.count == 4
+
+ changesets = conditions_bbox(changesets, *bbox_array)
elsif @params[:friends] && current_user
changesets = changesets.where(:user => current_user.followings.identifiable)
elsif @params[:nearby] && current_user
#------------------------------------------------------------
##
- # if a bounding box was specified do some sanity checks.
# restrict changesets to those enclosed by a bounding box
- def conditions_bbox(changesets, bbox)
- if bbox
- bbox.check_boundaries
- bbox = bbox.to_scaled
-
- changesets.where("min_lon < ? and max_lon > ? and min_lat < ? and max_lat > ?",
- bbox.max_lon.to_i, bbox.min_lon.to_i,
- bbox.max_lat.to_i, bbox.min_lat.to_i)
- else
+ def conditions_bbox(changesets, min_lon, min_lat, max_lon, max_lat)
+ db_min_lat = (min_lat * GeoRecord::SCALE).to_i
+ db_max_lat = (max_lat * GeoRecord::SCALE).to_i
+ db_min_lon = (wrap_lon(min_lon) * GeoRecord::SCALE).to_i
+ db_max_lon = (wrap_lon(max_lon) * GeoRecord::SCALE).to_i
+
+ changesets = changesets.where("min_lat < ? and max_lat > ?", db_max_lat, db_min_lat)
+
+ if max_lon - min_lon >= 360
+ # the query bbox spans the entire world, therefore no lon checks are necessary
changesets
+ elsif db_min_lon <= db_max_lon
+ # the normal case when the query bbox doesn't include the antimeridian
+ changesets.where("min_lon < ? and max_lon > ?", db_max_lon, db_min_lon)
+ else
+ # the query bbox includes the antimeridian
+ # this case works as if there are two query bboxes:
+ # [-180*SCALE .. db_max_lon], [db_min_lon .. 180*SCALE]
+ # it would be necessary to check if changeset bboxes intersect with either of the query bboxes:
+ # (changesets.min_lon < db_max_lon and changesets.max_lon > -180*SCALE) or (changesets.min_lon < 180*SCALE and changesets.max_lon > db_min_lon)
+ # but the comparisons with -180*SCALE and 180*SCALE are unnecessary:
+ # (changesets.min_lon < db_max_lon) or (changesets.max_lon > db_min_lon)
+ changesets.where("min_lon < ? or max_lon > ?", db_max_lon, db_min_lon)
end
end
+ def wrap_lon(lon)
+ ((lon + 180) % 360) - 180
+ end
+
##
# eliminate empty changesets (where the bbox has not been set)
# this should be applied to all changeset list displays
title: ערכות שינויים
title_user: ערכות שינויים מאת %{user}
title_user_link_html: ערכות שינויים מאת %{user_link}
- title_followed: ער×\9b×\95ת ש×\99× ×\95×\99×\99×\9d ש×\9c ×\9eשת×\9eש×\99×\9d ×\91ר×\99שמת המעקב שלך
+ title_followed: ער×\9b×\95ת ש×\99× ×\95×\99×\99×\9d ש×\9c ×\9eשת×\9eש×\99×\9d ×\91רש×\99מת המעקב שלך
title_nearby: ערכות שינויים של משתמשים בסביבה
empty: לא נמצאה אף ערכת שינויים.
empty_area: אין ערכות שינויים באזור הזה.
no_more: לא נמצאו ערכות שינויים נוספות.
no_more_area: אין עוד ערכות שינויים באזור הזה.
no_more_user: אין ערכות שינויים נוספות מאת המשתמש הזה.
+ older_changesets: ערכות שינויים ישנות יותר
+ newer_changesets: ערכות שינויים חדשות יותר
feed:
title: ערכת שינויים %{id}
title_comment: ערכת שינויים %{id} – %{comment}
agreed_with_pd: Исто така изјавивте дека вашите уредувања ги сметате за јавна
сопственост.
link text: што е ова?
+ not_agreed_with_pd: Исто така, немате изјавено дека вашите уредувања ги сметате
+ за јавна сопственост.
pd_link_text: изјави
save changes button: Зачувај ги промените
delete_account: Избриши сметка...
terms_declined_url: https://wiki.openstreetmap.org/wiki/Contributor_Terms_Declined?uselang=mk
pd_declarations:
show:
+ title: Сметај ги моите придонеси за јавна сопственост
+ consider_pd: Сметајте ги моите придонеси за јавна сопственост.
+ consider_pd_why: Зошто придонесите да ми бидат во јавна сопственост?
confirm: Потврди
+ create:
+ successfully_declared: Успешно изјавивте дека вашите уредувања ги сметате
+ за јавна сопственост.
+ already_declared: Веќе изјавивте дека вашите уредувања ги сметате за јавна
+ сопственост.
+ did_not_confirm: Не потврдивте дека вашите уредувања ги сметате за јавна сопственост.
browse:
deleted_ago_by_html: Избришано %{time_ago} од %{user}
edited_ago_by_html: Изменето %{time_ago} од %{user}
start_rjs:
feature_warning: Вчитувам %{num_features} елементи, што може да ви го забави
прелистувачот. Дали сте сигурни дека сакате да се прикажат овие податоци?
+ feature_error: 'Функциите не можеа да се вчитаат: %{message}'
load_data: Вчитај ги податоците
loading: Вчитувам...
tag_details:
sorry: За жал, на списокот на измени што го побаравте му требаше предолго
за да се преземе.
changesets:
+ changeset:
+ comments:
+ one: '%{count} коментар'
+ other: '%{count} коментари'
+ changes:
+ one: '%{count} промена'
+ other: '%{count} промени'
index:
title: Измени
title_user: Измени на %{user}
title_user_link_html: Промени од %{user_link}
+ title_followed: Измени од корисници што ги следите
title_nearby: Измени од соседни корисници
empty: Не пронајдов промени.
empty_area: Нема промени на ова подрачје.
за да гледате корисници во близина.'
edit_your_profile: Уредете си го профилот
followings: Корисници што ги следите
+ no followings: Сè уште не следите ниеден корисник.
nearby users: Други соседни корисници
no nearby users: Сè уште нема други корисници во вашата околина што признаваат
дека работат на карти.
use_map_link: На карта
index:
title: Дневници на корисници
+ title_followed: Дневници на следени корисници
title_nearby: Дневници на соседните корисници
user_title: Дневникот на %{user}
in_language_title: Дневнички написи на %{language}
title: Проблеми
select_status: Одберете статус
select_type: Одберете тип
+ select_last_managed_by: Одберете подследен управувач
reported_user: Пријавен корисник
+ not_managed: Не се управува
search: Пребарај
search_guidance: 'Пребарајте проблеми:'
states:
status: Статус
reports: Пријави
last_updated: Последна поднова
+ last_managed: Последен управувач
+ reporting_users: Пријавување на корисници
reports_count:
one: '%{count} пријава'
other: '%{count} пријави'
reopened: Состојбата на проблемот е поставена како „Отворено“
comments:
comment_from_html: Коментар од %{user_link} на %{comment_created_at}
+ reassign_to_moderators: Преназначете го проблемот на модераторите
+ reassign_to_administrators: Преназначете го проблемот на администраторите
reports:
reported_by_html: Пријавено како %{category} од %{user} на %{updated_at}
helper:
reportable_title:
diary_comment: '%{entry_title}, коментар бр. %{comment_id}'
note: Напомена бр. %{note_id}
+ reportable_heading:
+ diary_comment_html: Дневнички коментар %{title} создаден на %{datetime_created},
+ подновен на %{datetime_updated}
+ diary_entry_html: Дневничка ставка %{title} создадена на %{datetime_created},
+ подновена на %{datetime_updated}
+ note_html: '%{title} создадено на %{datetime_created}, подновено на %{datetime_updated}'
+ user_html: Корисник %{title} создаден на %{datetime_created}
+ reporters:
+ index:
+ title: Пријавувачи на проблем бр. %{issue_id}
+ reporters:
+ more_reporters: и уште %{count}
issue_comments:
create:
comment_created: Коментарот е успешно создаден
communities: Заедници
learn_more: Дознајте повеќе
more: Повеќе
+ offline_flash:
+ osm_offline: Базата на податоци на OpenStreetMap моментално е исклучена додека
+ работиме на неопходни одржувања.
+ osm_read_only: Базата на податоци на OpenStreetMap моментално може само да се
+ чита, додека ги извршиме неопходните одржувања.
+ expected_restore_html: Се очекува услугата да се поврати за %{time}.
+ announcement: Тука можете да го прочитате соопштението.
user_mailer:
diary_comment_notification:
description: Дневничка ставка бр. %{id} во OpenStreetMap
порака на авторот на %{replyurl}
follow_notification:
hi: Здраво %{to_user},
+ subject: '[OpenStreetMap] %{user} ве следи'
+ followed_you: '%{user} не ве следи на OpenStreetMap.'
see_their_profile: Можете да го погледате профилот на оваа личност на %{userurl}.
see_their_profile_html: Можете да го погледате профилот на оваа личност на %{userurl}.
+ follow_them: Можете да го следите лицето и на %{followurl}.
+ follow_them_html: Можете да го следите лицето и на %{followurl}.
+ gpx_details:
+ details: 'Поединости за вашата податотека:'
+ filename: Име на податотеката
+ url: URL
+ description: Опис
+ tags: Ознаки
+ total_points: Вкупно точки
+ imported_points: Број на увезени точки
gpx_failure:
hi: Здраво %{to_user},
- failed_to_import: 'не можеше да се увезе. Проверете дали е важечка GPX-податотека
- или архив што содржи GPX-податотека/ки во поддржан формат (.tar.gz, .tar.bz2,
- .tar, .zip, .gpx.gz, .gpx.bz2). Да не има форматен или синтаксен проблем со
- податотеката? Еве ја гршеката при увоз:'
- more_info: Повеќе информации за неуспесите на увозот на GPX и тоа како да ги
- одбегнете ќе најдете на %{url}.
+ failed_to_import: Изгледа дека вашата податотека не успеа да се увезе како ГПС-трага.
+ verify: 'Проверете дали е важечка GPX-податотека или архив што содржи GPX-податотека/ки
+ во поддржан формат (.tar.gz, .tar.bz2, .tar, .zip, .gpx.gz, .gpx.bz2). Да
+ не има форматен или синтаксен проблем со податотеката? Еве ја гршеката при
+ увоз:'
+ more_info: Повеќе информации за неуспесите на увозот на GPX и за тоа како да
+ ги одбегнете ќе најдете на %{url}.
more_info_html: Повеќе информации за неуспесите на увозот на GPX и тоа како
да ги одбегнете ќе најдете на %{url}.
import_failures_url: https://wiki.openstreetmap.org/wiki/GPX_Import_Failures?uselang=mk
subject: '[OpenStreetMap] Проблем при увозот на GPX податотека'
gpx_success:
hi: Здраво %{to_user},
+ imported_successfully: Изгледа дека вашата податотека е успешно увезена како
+ ГПС-трага.
all_your_traces: Сите ваши успешно подигнати GPX-траги ќе ги најдете на %{url}
all_your_traces_html: Сите ваши успешно подигнати GPX-траги ќе ги најдете на
%{url}.
preview: Преглед
help: Помош
pagination:
+ changeset_comments:
+ older: Постари коментари
+ newer: Понови коментари
diary_comments:
older: Постари коментари
newer: Понови коментари
openid: Најава со OpenStreetMap
read_prefs: Кориснички нагодувања за читање
write_prefs: Менување на корисничките нагодувања
- write_diary: Создавање на дневнички ставки, коментирање и спријателување
+ write_diary: Создавање на дневнички ставки и коментири
write_api: Менување на картата
write_changeset_comments: Коментирање на измени
read_gpx: Читање на приватни ГПС-траги
другите картографи за да ја средиме работата. Поместете го бележникот на исправното
место и внесете порака, објаснувајќи го проблемот.
anonymous_warning_html: Не сте најавени. %{log_in} или %{sign_up} ако сакате
- да ве известуваме за вашата белешка.
+ да ве известуваме за вашата белешка и да им помогнете на картографите да ја
+ решат.
anonymous_warning_log_in: Најавете се
anonymous_warning_sign_up: зачленете се
counter_warning_html: Веќе имате објавено барем %{x_anonymous_notes}. Тоа е
heading: सर्तहरू
heading_ct: योगदानकर्ता सर्तहरू
continue: जारी राख्ने
+ cancel: रद्द गर्नुहोस्
legale_names:
france: फ्रान्स
italy: इटाली
title: Wijzigingensets
title_user: Wijzigingensets door %{user}
title_user_link_html: Wijzigingensets door %{user_link}
- title_followed: Wijzigingensets door gevolgde kaartmakers
+ title_followed: Wijzigingensets door gebruikers die u volgt
title_nearby: Wijzigingensets van gebruikers in de buurt
empty: Geen wijzigingensets gevonden.
empty_area: Geen wijzigingensets in dit gebied.
no_home_location_html: '%{edit_profile_link} en stel uw thuislocatie in om gebruikers
in de buurt te zien.'
edit_your_profile: Pas uw profiel aan
- followings: Gevolgde kaartmakers
+ followings: Gebruikers die u volgt
no followings: Je volgt nog geen andere kaartmakers.
nearby users: Andere nabije gebruikers
no nearby users: Er zijn nog geen andere gebruikers die hebben opgegeven in
use_map_link: Kaart gebruiken
index:
title: Gebruikersdagboeken
- title_followed: Dagboeken van gevolgde kaartmakers
+ title_followed: Dagboeken van gevolgde gebruikers
title_nearby: Dagboeken van gebruikers in de buurt
user_title: Dagboek van %{user}
in_language_title: Dagboekberichten in het %{language}
van de kaart.
credit_4_1_this_copyright_page: deze auteursrechtpagina
attribution_example:
- alt: Voorbeeld van hoe de naamsvermelding voor OpenStreetMap toe te passen
- op een webpagina
+ alt: Voorbeeld van een naamsvermelding van OpenStreetMap op een webpagina
title: Voorbeeld naamsvermelding
more_title_html: Meer informatie
more_1_1_html: Lees meer over het gebruik van onze gegevens en hoe u ons kunt
# Author: Naoliv
# Author: Nemo bis
# Author: Nighto
+# Author: Ninawauwau
# Author: Pedrofariasm
# Author: Re demz
# Author: Rodrigo Avila
title: Conjuntos de alterações
title_user: Conjuntos de alterações de %{user}
title_user_link_html: Conjuntos de alterações de %{user_link}
- title_followed: Conjuntos de alterações das pessoas que sigo
+ title_followed: Conjuntos de alterações das pessoas seguidas
title_nearby: Conjuntos de alterações de usuários próximos
empty: Nenhum conjunto de alterações encontrado.
empty_area: Nenhum conjunto de alterações nesta área.
osmchangexml: XML osmChange
paging_nav:
nodes_paginated: Pontos (%{x}-%{y} de %{count})
+ ways_title: Rotas
ways_paginated: Linhas (%{x}-%{y} de %{count})
relations_paginated: Relações (%{x}-%{y} de %{count})
not_found_message:
no_home_location_html: '%{edit_profile_link} e defina seu local de origem para
ver usuários próximos.'
edit_your_profile: Editar seu perfil
- followings: Pessoas que sigo
+ followings: Pessoas que você segue
no followings: Você ainda não seguiu nenhum usuário.
nearby users: Outros usuários próximos
no nearby users: Ainda não há outros usuários mapeando por perto.
use_map_link: Usar mapa
index:
title: Diários dos usuários
- title_followed: Diários das pessoas que sigo
+ title_followed: Diários das pessoas seguidas
title_nearby: Diários dos usuários próximos
user_title: Diário de %{user}
in_language_title: Publicações no diário em %{language}
fishpond: Tanque de peixes
lagoon: Lagoa
wastewater: Agua residual
+ oxbow: Braço morto
lock: Eclusa
waterway:
artificial: Via Aquática Artificial
weir: Vertedouro
"yes": Via Aquática
admin_levels:
- level2: Fronteira nacional
+ level2: Fronteira Nacional
level3: Limite de região
level4: Divisa Estadual
level5: Limite Regional
level9: Limite de Distrito Municipal
level10: Limite de Bairro
level11: Limite da vizinhança
+ border_types:
+ city: Limite de município
+ comarca: Limite de comarca
+ county: Limite de província
+ departement: Limite de departamento
+ department: Limite de departamento
+ district: Limite de distrito
+ distrito: Limite de distrito
+ freguesia: Limite de freguesia
results:
no_results: Nenhum resultado encontrado
more_results: Mais resultados
where_am_i_title: Descrever a localidade atual usando o motor de busca
submit_text: Ir
reverse_directions_text: Sentido contrário
+ modes:
+ bicycle: Bicicleta
+ car: Carro
+ foot: A pé
welcome:
title: Bem-vindo(a)!
introduction: Bem-vindo(a) ao OpenStreetMap, o mapa livre e editável do mundo.
ninth: 9.ª
tenth: 10.ª
time: Duração
+ filename: rota
query:
node: Ponto
way: Linha
query_features: Consultar elementos
centre_map: Centralizar o mapa aqui
home:
- marker_title: A localização da minha casa
- not_set: A localização da casa não está definido para sua conta
+ marker_title: Localização da minha casa
+ not_set: Localização da casa não está definida para sua conta
redactions:
edit:
heading: Editar anulação
title: Набори змін
title_user: Набори змін від %{user}
title_user_link_html: Набори змін від %{user_link}
- title_followed: Набори змін від тих за ким слідкуєш
+ title_followed: Набори змін від тих за ким стежиш
title_nearby: Набори змін маперів поряд з вами
empty: Жодного набору змін не знайдено.
empty_area: На цій ділянці набори змін відсутні.
no_more: Наборів змін більше не знайдено.
no_more_area: Наборів змін на цій ділянці більше немає.
no_more_user: Наборів змін від цього мапера більше немає.
+ older_changesets: Старіші набори змін
+ newer_changesets: Новіші набори змін
feed:
title: Набір змін %{id}
title_comment: Набір змін %{id} — %{comment}
no_home_location_html: '%{edit_profile_link} і встановіть своє місце розташування,
щоб бачити маперів поруч.'
edit_your_profile: Редагувати свій профіль
- followings: Ð\92Ñ\96дÑ\81лÑ\96дковÑ\83Ñ\94Ñ\88
+ followings: Ð\9aоÑ\80иÑ\81Ñ\82Ñ\83ваÑ\87Ñ\96, за Ñ\8fкими ви Ñ\81Ñ\82ежиÑ\82е
no followings: Ви поки що не слідкуєте за жодним учасником.
nearby users: Інші мапери поруч
no nearby users: Поблизу поки немає маперів, які позначили своє розташування.
use_map_link: Вказати на мапі
index:
title: Щоденники учасників
- title_followed: Щоденники тих, за ким слідкуєш
+ title_followed: Щоденники тих, за ким стежиш
title_nearby: Щоденники учасників поряд з вами
user_title: Щоденник %{user}
in_language_title: Записи щоденника мовою %{language}
footway: Пішохідна доріжка
ford: Брід
give_way: Знак Дати путь
- living_street: Ð\96иÑ\82лова вÑ\83лиÑ\86Ñ\8f
+ living_street: Ð\96иÑ\82лова зона
milestone: Кілометровий стовпчик
motorway: Автомагістраль
motorway_junction: В’їзд на автомагістраль
information:
guidepost: Вказівник напрямку
board: Інформаційна дошка
- map: Ð\9aаÑ\80Ñ\82а
+ map: Ð\9cапа
office: Туристичний офіс
terminal: Інформаційний термінал
sign: Інформаційний знак
car_repair: Автомайстерня
carpet: Килими
charity: Соціальний магазин
- cheese: СиÑ\80ний магазин
+ cheese: Ð\9cагазин Ñ\81иÑ\80Ñ\83
chemist: Побутова хімія
chocolate: Шоколад
clothes: Одяг
api:
notes:
comment:
- opened_at_html: 於%{when}建立
- opened_at_by_html: 於%{when}由%{user}建立
+ opened_at_html: 於 %{when} 建立
+ opened_at_by_html: 於 %{when} 由 %{user} 建立
commented_at_html: 於%{when}更新
- commented_at_by_html: 於%{when}由%{user}更新
- closed_at_html: 於%{when}已解決
- closed_at_by_html: 於%{when}由%{user}關閉
- reopened_at_html: 於%{when}重新開啟
- reopened_at_by_html: 於%{when}由%{user}重新開啟
+ commented_at_by_html: 於 %{when} 由 %{user} 更新
+ closed_at_html: 於 %{when} 已解決
+ closed_at_by_html: 於 %{when} 由 %{user} 關閉
+ reopened_at_html: 於 %{when} 重新開啟
+ reopened_at_by_html: 於 %{when} 由 %{user} 重新開啟
rss:
title: 開放街圖註記
description_all: 報告、評論、或關閉的註記清單
already_declared: 你已經宣稱你的貢獻屬於公共領域了。
did_not_confirm: 你仍未宣稱你的貢獻屬於公共領域。
browse:
- deleted_ago_by_html: 由%{user}%{time_ago}刪除
- edited_ago_by_html: 由%{user}%{time_ago}編輯
+ deleted_ago_by_html: 由 %{user} %{time_ago} 刪除
+ edited_ago_by_html: 由 %{user} %{time_ago} 編輯
version: 版本
redacted_version: 編修版本
in_changeset: 變更集
feeds:
comment:
comment: '由 %{author} 對變更集 #%{changeset_id} 發表的新評論'
- commented_at_by_html: 於%{when}由%{user}更新
+ commented_at_by_html: 於 %{when} 由 %{user} 更新
show:
title_all: 開放貼圖變更集討論
title_particular: 開放街圖變更集 %{changeset_id} 討論
title: 變更集
title_user: '%{user} 的變更集'
title_user_link_html: '%{user_link} 的變更集'
- title_followed: 追蹤者的變更集
+ title_followed: 你追蹤的使用者的變更集
title_nearby: 附近使用者的變更集
empty: 查無變更集。
empty_area: 此區域沒有變更集。
no_more: 查無更多變更集。
no_more_area: 此區域沒有更多變更集。
no_more_user: 此使用者沒有更多變更集。
+ older_changesets: 較舊的變更集
+ newer_changesets: 較新的變更集
feed:
title: 變更集 %{id}
title_comment: 變更集 %{id} - %{comment}
closed: 關閉於:%{when}
created_ago_html: '%{time_ago}建立'
closed_ago_html: '%{time_ago}關閉'
- created_ago_by_html: 由%{user}%{time_ago}建立
- closed_ago_by_html: 由%{user}%{time_ago}關閉
+ created_ago_by_html: 由 %{user} %{time_ago} 建立
+ closed_ago_by_html: 由 %{user} %{time_ago} 關閉
discussion: 討論
join_discussion: 登入以參加討論
still_open: 變更集仍為開啟 - 討論要在變更集關閉後才會開啟。
subscribe: 訂閱
unsubscribe: 取消訂閱
- comment_by_html: 來自%{user}%{time_ago}的評論
- hidden_comment_by_html: '%{user}%{time_ago}隱藏評論'
+ comment_by_html: 來自 %{user} %{time_ago} 的評論
+ hidden_comment_by_html: '%{user} %{time_ago} 隱藏評論'
hide_comment: 隱藏
unhide_comment: 取消隱藏
comment: 評論
contact:
km away: '%{count} 公里遠'
m away: '%{count} 公尺遠'
- latest_edit_html: 上次編輯於%{ago}:
+ latest_edit_html: 上次編輯於 %{ago}:
no_edits: (沒有編輯)
view_changeset_details: 檢視變更集詳細資料
popup:
title: 我的功能面板
no_home_location_html: '%{edit_profile_link}並編輯你的家位置,來查看附近的使用者。'
edit_your_profile: 編輯你的個人檔案
- followings: 追蹤者
+ followings: 你追蹤的使用者
no followings: 您尚未追蹤任何使用者。
nearby users: 其他附近的使用者
no nearby users: 附近沒有已加入製圖的使用者。
use_map_link: 使用地圖
index:
title: 使用者日記
- title_followed: 追蹤者的日記
+ title_followed: 你追蹤的使用者的日記
title_nearby: 附近的使用者的日記
user_title: '%{user} 的日記'
in_language_title: 語言為%{language}的日記項目
subscribe: 訂閱
unsubscribe: 取消訂閱
leave_a_comment: 留下評論
- login_to_leave_a_comment_html: '%{login_link}來留下評論'
+ login_to_leave_a_comment_html: '%{login_link} 來留下評論'
login: 登入
no_such_entry:
title: 沒有這樣的日記項目
title: 錯誤請求
description: 你在開放街圖伺服器上請求的操作無效 (HTTP 400)
forbidden:
- title: Forbidden
+ title: 禁止
description: 你在開放街圖伺服器上請求的運作僅限管理員使用 (HTTP 403}
internal_server_error:
title: 應用程式錯誤
path: 小徑
pedestrian: 人行道
platform: 月台
- primary: 省道
- primary_link: 省道聯絡道
+ primary: 一級道路
+ primary_link: 一級道路聯絡道
proposed: 計畫中道路
raceway: 賽道
residential: 住宅區道路
rest_area: 休息區
road: 道路
- secondary: 縣道
- secondary_link: 縣道聯絡道
- service: 專用道路
+ secondary: 二級道路
+ secondary_link: 次要道路聯絡道
+ service: 服務道路
services: 高速公路服務區
speed_camera: 測速相機
steps: 階梯
stop: 停止標誌
street_lamp: 路燈
- tertiary: 鄉道
- tertiary_link: 鄉道聯絡道
+ tertiary: 三級道路
+ tertiary_link: 三級道路聯絡道
track: 產業道路
traffic_mirror: 道路反射鏡
traffic_signals: 交通號誌
motorway: 高速公路
main_road: 主要幹道
trunk: 快速公路
- primary: 省道
- secondary: 縣道
+ primary: 一級道路
+ secondary: 二級道路
unclassified: 未分級道路
pedestrian: 人行道
track: 產業道路
identifiable: 可辨識
private: 私人
trackable: 可追蹤
- details_with_tags_html: 由%{user}在%{tags}於%{time_ago}
- details_without_tags_html: 由%{user}於%{time_ago}
+ details_with_tags_html: 由 %{user} 在 %{tags} 於 %{time_ago}
+ details_without_tags_html: 由 %{user} 於 %{time_ago}
index:
public_traces: 公開 GPS 軌跡
my_gps_traces: 我的 GPS 軌跡
comment: 評論
diary_comments:
index:
- title: 日記評論由%{user}新增
+ title: 日記評論由 %{user} 新增
page:
post: 貼文
when: 於
navigation:
all_blocks: 所有封鎖
blocks_on_me: 對我的封鎖
- blocks_on_user_html: 對%{user}的封鎖
+ blocks_on_user_html: 對 %{user} 的封鎖
blocks_by_me: 由我做出的封鎖
- blocks_by_user_html: 由%{user}做出的封鎖
+ blocks_by_user_html: 由 %{user} 做出的封鎖
block: 封鎖#%{id}
new_block: 新封鎖
user_mutes:
index:
title: 由 %{user} 送出或評論的註記
heading: '%{user} 的註記'
- subheading_html: 由%{user}%{submitted}或%{commented}的註記
+ subheading_html: 由 %{user} %{submitted} 或 %{commented} 的註記
subheading_submitted: 已提交
subheading_commented: 已評論
no_notes: 沒有註記
hidden_title: 已隱藏的註記:#%{note_name}
description_when_author_is_deleted: 已刪除
description_when_there_is_no_opening_comment: 未知的
- event_opened_by_html: 由%{user}%{time_ago}建立
+ event_opened_by_html: 由 %{user} %{time_ago} 建立
event_opened_by_anonymous_html: 由匿名使用者%{time_ago}建立
- event_commented_by_html: 來自%{user}%{time_ago}的評論
+ event_commented_by_html: 來自 %{user} %{time_ago} 的評論
event_commented_by_anonymous_html: 來自匿名使用者%{time_ago}的評論
- event_closed_by_html: 由%{user}%{time_ago}解決
+ event_closed_by_html: 由 %{user} %{time_ago} 解決
event_closed_by_anonymous_html: 由匿名使用者%{time_ago}解決
- event_reopened_by_html: 由%{user} %{time_ago}重新開啟
+ event_reopened_by_html: 由 %{user} %{time_ago} 重新開啟
event_reopened_by_anonymous_html: 由匿名使用者%{time_ago}重新開啟
- event_hidden_by_html: 由%{user} %{time_ago}隱藏
+ event_hidden_by_html: 由 %{user} %{time_ago} 隱藏
report: 回報此註記
anonymous_warning: 此項註記包含來自匿名使用者的評論,應作獨立核實。
discussion: 討論
user = create(:user)
changeset = create(:changeset, :user => user)
closed_changeset = create(:changeset, :closed, :user => user, :created_at => Time.utc(2008, 1, 1, 0, 0, 0), :closed_at => Time.utc(2008, 1, 2, 0, 0, 0))
- changeset2 = create(:changeset, :min_lat => (5 * GeoRecord::SCALE).round, :min_lon => (5 * GeoRecord::SCALE).round, :max_lat => (15 * GeoRecord::SCALE).round, :max_lon => (15 * GeoRecord::SCALE).round)
- changeset3 = create(:changeset, :min_lat => (4.5 * GeoRecord::SCALE).round, :min_lon => (4.5 * GeoRecord::SCALE).round, :max_lat => (5 * GeoRecord::SCALE).round, :max_lon => (5 * GeoRecord::SCALE).round)
+ changeset2 = create(:changeset, :bbox => [5, 5, 15, 15])
+ changeset3 = create(:changeset, :bbox => [4.5, 4.5, 5, 5])
get api_changesets_path(:bbox => "-10,-10, 10, 10")
assert_response :success, "can't get changesets in bbox"
end
def test_show_bbox_json
- # test bbox attribute
- changeset = create(:changeset, :min_lat => (-5 * GeoRecord::SCALE).round, :min_lon => (5 * GeoRecord::SCALE).round,
- :max_lat => (15 * GeoRecord::SCALE).round, :max_lon => (12 * GeoRecord::SCALE).round)
+ changeset = create(:changeset, :bbox => [5, -5, 12, 15])
get api_changeset_path(changeset, :format => "json")
assert_response :success, "cannot get first changeset"
create(:changeset, :user => user, :created_at => Time.now.utc - 7.days)
# create a changeset that puts us near the initial size limit
- changeset = create(:changeset, :user => user,
- :min_lat => (-0.5 * GeoRecord::SCALE).round, :min_lon => (0.5 * GeoRecord::SCALE).round,
- :max_lat => (0.5 * GeoRecord::SCALE).round, :max_lon => (2.5 * GeoRecord::SCALE).round)
+ changeset = create(:changeset, :user => user, :bbox => [0.5, -0.5, 2.5, 0.5])
# create authentication header
auth_header = bearer_authorization_header user
##
# This should display the last 20 changesets closed in a specific area
def test_index_bbox
- changesets = create_list(:changeset, 10, :num_changes => 1, :min_lat => 50000000, :max_lat => 50000001, :min_lon => 50000000, :max_lon => 50000001)
- other_changesets = create_list(:changeset, 10, :num_changes => 1, :min_lat => 0, :max_lat => 1, :min_lon => 0, :max_lon => 1)
+ changesets = create_list(:changeset, 10, :num_changes => 1, :bbox => [5, 5, 5, 5])
+ other_changesets = create_list(:changeset, 10, :num_changes => 1, :bbox => [0, 0, 1, 1])
# First check they all show up without a bbox parameter
get history_path(:format => "html", :list => "1"), :xhr => true
check_index_result(changesets)
end
+ def test_index_bbox_across_antimeridian_with_changesets_close_to_antimeridian
+ west_of_antimeridian_changeset = create(:changeset, :num_changes => 1, :bbox => [176, 0, 178, 1])
+ east_of_antimeridian_changeset = create(:changeset, :num_changes => 1, :bbox => [-178, 0, -176, 1])
+
+ get history_path(:format => "html", :list => "1")
+ assert_response :success
+ check_index_result([east_of_antimeridian_changeset, west_of_antimeridian_changeset])
+
+ # negative longitudes
+ get history_path(:format => "html", :list => "1", :bbox => "-190,-10,-170,10")
+ assert_response :success
+ check_index_result([east_of_antimeridian_changeset, west_of_antimeridian_changeset])
+
+ get history_path(:format => "html", :list => "1", :bbox => "-183,-10,-177,10")
+ assert_response :success
+ check_index_result([east_of_antimeridian_changeset, west_of_antimeridian_changeset])
+
+ get history_path(:format => "html", :list => "1", :bbox => "-181,-10,-177,10")
+ assert_response :success
+ check_index_result([east_of_antimeridian_changeset])
+
+ get history_path(:format => "html", :list => "1", :bbox => "-183,-10,-179,10")
+ assert_response :success
+ check_index_result([west_of_antimeridian_changeset])
+
+ get history_path(:format => "html", :list => "1", :bbox => "-181,-10,-179,10")
+ assert_response :success
+ check_index_result([])
+
+ # positive longitudes
+ get history_path(:format => "html", :list => "1", :bbox => "170,-10,190,10")
+ assert_response :success
+ check_index_result([east_of_antimeridian_changeset, west_of_antimeridian_changeset])
+
+ get history_path(:format => "html", :list => "1", :bbox => "177,-10,183,10")
+ assert_response :success
+ check_index_result([east_of_antimeridian_changeset, west_of_antimeridian_changeset])
+
+ get history_path(:format => "html", :list => "1", :bbox => "177,-10,181,10")
+ assert_response :success
+ check_index_result([west_of_antimeridian_changeset])
+
+ get history_path(:format => "html", :list => "1", :bbox => "179,-10,183,10")
+ assert_response :success
+ check_index_result([east_of_antimeridian_changeset])
+
+ get history_path(:format => "html", :list => "1", :bbox => "179,-10,181,10")
+ assert_response :success
+ check_index_result([])
+ end
+
+ def test_index_bbox_across_antimeridian_with_changesets_around_globe
+ changeset1 = create(:changeset, :num_changes => 1, :bbox => [-150, 40, -140, 50])
+ changeset2 = create(:changeset, :num_changes => 1, :bbox => [-30, -30, -20, -20])
+ changeset3 = create(:changeset, :num_changes => 1, :bbox => [10, 60, 20, 70])
+ changeset4 = create(:changeset, :num_changes => 1, :bbox => [150, -60, 160, -50])
+
+ # no bbox, get all changesets
+ get history_path(:format => "html", :list => "1")
+ assert_response :success
+ check_index_result([changeset4, changeset3, changeset2, changeset1])
+
+ # large enough bbox within normal range
+ get history_path(:format => "html", :list => "1", :bbox => "-170,-80,170,80")
+ assert_response :success
+ check_index_result([changeset4, changeset3, changeset2, changeset1])
+
+ # bbox for [1,2] within normal range
+ get history_path(:format => "html", :list => "1", :bbox => "-160,-80,0,80")
+ assert_response :success
+ check_index_result([changeset2, changeset1])
+
+ # bbox for [1,4] containing antimeridian with negative lon
+ get history_path(:format => "html", :list => "1", :bbox => "-220,-80,-100,80")
+ assert_response :success
+ check_index_result([changeset4, changeset1])
+
+ # bbox for [1,4] containing antimeridian with positive lon
+ get history_path(:format => "html", :list => "1", :bbox => "100,-80,220,80")
+ assert_response :success
+ check_index_result([changeset4, changeset1])
+
+ # large enough bbox outside normal range
+ get history_path(:format => "html", :list => "1", :bbox => "-220,-80,220,80")
+ assert_response :success
+ check_index_result([changeset4, changeset3, changeset2, changeset1])
+ end
+
+ ##
+ # Test that -180..180 longitudes don't result in empty bbox
+ def test_index_bbox_entire_world
+ changeset = create(:changeset, :num_changes => 1, :bbox => [30, 60, 31, 61])
+
+ get history_path(:format => "html", :list => "1", :bbox => "-180,-80,-180,80")
+ assert_response :success
+ check_index_result([])
+
+ get history_path(:format => "html", :list => "1", :bbox => "180,-80,180,80")
+ assert_response :success
+ check_index_result([])
+
+ get history_path(:format => "html", :list => "1", :bbox => "-180,-80,180,80")
+ assert_response :success
+ check_index_result([changeset])
+ end
+
+ ##
+ # Test that -270..270 longitudes don't result in 90..-90 bbox
+ def test_index_bbox_larger_than_entire_world
+ changeset1 = create(:changeset, :num_changes => 1, :bbox => [30, 60, 31, 61])
+ changeset2 = create(:changeset, :num_changes => 1, :bbox => [130, 60, 131, 61])
+
+ get history_path(:format => "html", :list => "1", :bbox => "-90,-80,90,80")
+ assert_response :success
+ check_index_result([changeset1])
+
+ get history_path(:format => "html", :list => "1", :bbox => "-270,-80,270,80")
+ assert_response :success
+ check_index_result([changeset2, changeset1])
+ end
+
##
# Checks the display of the user changesets listing
def test_index_user
##
# This should display the last 20 changesets closed in a specific area
def test_feed_bbox
- changeset = create(:changeset, :num_changes => 1, :min_lat => 5 * GeoRecord::SCALE, :min_lon => 5 * GeoRecord::SCALE, :max_lat => 5 * GeoRecord::SCALE, :max_lon => 5 * GeoRecord::SCALE)
+ changeset = create(:changeset, :num_changes => 1, :bbox => [5, 5, 5, 5])
create(:changeset_tag, :changeset => changeset)
create(:changeset_tag, :changeset => changeset, :k => "website", :v => "http://example.com/")
- closed_changeset = create(:changeset, :closed, :num_changes => 1, :min_lat => 5 * GeoRecord::SCALE, :min_lon => 5 * GeoRecord::SCALE, :max_lat => 5 * GeoRecord::SCALE, :max_lon => 5 * GeoRecord::SCALE)
- _elsewhere_changeset = create(:changeset, :num_changes => 1, :min_lat => -5 * GeoRecord::SCALE, :min_lon => -5 * GeoRecord::SCALE, :max_lat => -5 * GeoRecord::SCALE, :max_lon => -5 * GeoRecord::SCALE)
- _empty_changeset = create(:changeset, :num_changes => 0, :min_lat => -5 * GeoRecord::SCALE, :min_lon => -5 * GeoRecord::SCALE, :max_lat => -5 * GeoRecord::SCALE, :max_lon => -5 * GeoRecord::SCALE)
+ closed_changeset = create(:changeset, :closed, :num_changes => 1, :bbox => [5, 5, 5, 5])
+ _elsewhere_changeset = create(:changeset, :num_changes => 1, :bbox => [-5, -5, -5, -5])
+ _empty_changeset = create(:changeset, :num_changes => 0, :bbox => [5, 5, 5, 5])
get history_feed_path(:format => :atom, :bbox => "4.5,4.5,5.5,5.5")
assert_response :success
FactoryBot.define do
factory :changeset do
+ transient do
+ bbox { nil }
+ end
+
created_at { Time.now.utc }
closed_at { Time.now.utc + 1.day }
+ min_lon { (bbox[0] * GeoRecord::SCALE).round if bbox }
+ min_lat { (bbox[1] * GeoRecord::SCALE).round if bbox }
+ max_lon { (bbox[2] * GeoRecord::SCALE).round if bbox }
+ max_lat { (bbox[3] * GeoRecord::SCALE).round if bbox }
user
class BrowseCommentLinksTest < ApplicationSystemTestCase
test "visiting changeset comment link should pan to changeset" do
- changeset = create(:changeset, :min_lat => 60 * GeoRecord::SCALE, :min_lon => 30 * GeoRecord::SCALE,
- :max_lat => 60 * GeoRecord::SCALE, :max_lon => 30 * GeoRecord::SCALE)
+ changeset = create(:changeset, :bbox => [30, 60, 30, 60])
comment = create(:changeset_comment, :changeset => changeset, :body => "Linked changeset comment")
visit changeset_path(changeset, :anchor => "c#{comment.id}")
end
test "update sidebar when before param is included and map is moved" do
- changeset1 = create(:changeset, :num_changes => 1, :min_lat => 50000000, :max_lat => 50000001, :min_lon => 50000000, :max_lon => 50000001)
+ changeset1 = create(:changeset, :num_changes => 1, :bbox => [5, 5, 5, 5])
create(:changeset_tag, :changeset => changeset1, :k => "comment", :v => "changeset-at-fives")
- changeset2 = create(:changeset, :num_changes => 1, :min_lat => 50100000, :max_lat => 50100001, :min_lon => 50100000, :max_lon => 50100001)
+ changeset2 = create(:changeset, :num_changes => 1, :bbox => [5.01, 5.01, 5.01, 5.01])
create(:changeset_tag, :changeset => changeset2, :k => "comment", :v => "changeset-close-to-fives")
changeset3 = create(:changeset)
test "all changesets are listed when fully zoomed out" do
user = create(:user)
[-177, -90, 0, 90, 177].each do |lon|
- create(:changeset, :user => user, :num_changes => 1,
- :min_lat => 0 * GeoRecord::SCALE, :min_lon => (lon - 1) * GeoRecord::SCALE,
- :max_lat => 1 * GeoRecord::SCALE, :max_lon => (lon + 1) * GeoRecord::SCALE) do |changeset|
+ create(:changeset, :user => user, :num_changes => 1, :bbox => [lon - 1, 0, lon + 1, 1]) do |changeset|
create(:changeset_tag, :changeset => changeset, :k => "comment", :v => "changeset-at-lon(#{lon})")
end
end
end
end
+ test "changesets at both sides of antimeridian are listed" do
+ user = create(:user)
+ PAGE_SIZE.times do
+ create(:changeset, :user => user, :num_changes => 1, :bbox => [176, 0, 178, 1]) do |changeset|
+ create(:changeset_tag, :changeset => changeset, :k => "comment", :v => "West-of-antimeridian-changeset")
+ end
+ create(:changeset, :user => user, :num_changes => 1, :bbox => [-178, 0, -176, 1]) do |changeset|
+ create(:changeset_tag, :changeset => changeset, :k => "comment", :v => "East-of-antimeridian-changeset")
+ end
+ end
+
+ visit history_path(:anchor => "map=6/0/179")
+
+ within_sidebar do
+ assert_link "West-of-antimeridian-changeset", :count => PAGE_SIZE / 2
+ assert_link "East-of-antimeridian-changeset", :count => PAGE_SIZE / 2
+
+ click_on "Older Changesets"
+
+ assert_link "West-of-antimeridian-changeset", :count => PAGE_SIZE
+ assert_link "East-of-antimeridian-changeset", :count => PAGE_SIZE
+ end
+ end
+
private
def create_visible_changeset(user, comment)