mirror of
https://github.com/noncomputable/AgentMaps.git
synced 2026-02-01 17:27:27 +00:00
updated tooling
This commit is contained in:
parent
0730fcce5a
commit
91b1279a42
@ -1,8 +1,8 @@
|
||||
/* The Agentmap class, which turns a Leaflet map into a simulation platform. */
|
||||
|
||||
let L = require("./featuretool").L,
|
||||
lineSlice = require('@turf/line-slice').default,
|
||||
length = require('@turf/length').default;
|
||||
lineSlice = require("@turf/line-slice").default,
|
||||
length = require("@turf/length").default;
|
||||
|
||||
/**
|
||||
* The main class for building, storing, simulating, and manipulating agent-based models on Leaflet maps.
|
||||
@ -22,22 +22,22 @@ length = require('@turf/length').default;
|
||||
* @property {number} animation_interval - The number of steps agents must move before being redrawn. Given 1, they will be redrawn after every step. Given 0, the animation will not update at all. 1 by default. Will be a nonnegative integer.
|
||||
* @property {?function} controller - User-defined function to be called on each update.
|
||||
*/
|
||||
Agentmap = function(map, animation_interval = 1) {
|
||||
Agentmap.checkAnimIntervalOption(animation_interval);
|
||||
Agentmap = function (map, animation_interval = 1) {
|
||||
Agentmap.checkAnimIntervalOption(animation_interval);
|
||||
|
||||
this.map = map,
|
||||
this.units = null,
|
||||
this.streets = null,
|
||||
this.agents = null,
|
||||
this.pathfinder = null,
|
||||
this.state = {
|
||||
running: false,
|
||||
paused: false,
|
||||
animation_frame_id: null,
|
||||
ticks: null,
|
||||
},
|
||||
this.controller = function() {},
|
||||
this.animation_interval = animation_interval
|
||||
(this.map = map),
|
||||
(this.units = null),
|
||||
(this.streets = null),
|
||||
(this.agents = null),
|
||||
(this.pathfinder = null),
|
||||
(this.state = {
|
||||
running: false,
|
||||
paused: false,
|
||||
animation_frame_id: null,
|
||||
ticks: null,
|
||||
}),
|
||||
(this.controller = function () {}),
|
||||
(this.animation_interval = animation_interval);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -45,13 +45,13 @@ Agentmap = function(map, animation_interval = 1) {
|
||||
*
|
||||
* @param {number} animation_interval - The desired animation interval to give the simulation. Must be a nonnegative integer.
|
||||
*/
|
||||
Agentmap.prototype.setAnimationInterval = function(animation_interval) {
|
||||
Agentmap.checkAnimIntervalOption(animation_interval);
|
||||
Agentmap.prototype.setAnimationInterval = function (animation_interval) {
|
||||
Agentmap.checkAnimIntervalOption(animation_interval);
|
||||
|
||||
this.animation_interval = animation_interval;
|
||||
this.animation_interval = animation_interval;
|
||||
|
||||
this.agents.eachLayer(agent => agent.setLatLng(agent._latlng));
|
||||
}
|
||||
this.agents.eachLayer((agent) => agent.setLatLng(agent._latlng));
|
||||
};
|
||||
|
||||
/**
|
||||
* Check whether the animation interval option provided is valid.
|
||||
@ -59,74 +59,71 @@ Agentmap.prototype.setAnimationInterval = function(animation_interval) {
|
||||
*
|
||||
* @param {number} animation_interval - An input specifying an animation interval distance.
|
||||
*/
|
||||
Agentmap.checkAnimIntervalOption = function(animation_interval) {
|
||||
if (!Number.isInteger(animation_interval) && animation_interval >= 0) {
|
||||
throw new Error("The animation_interval must be a non-negative integer!");
|
||||
}
|
||||
}
|
||||
Agentmap.checkAnimIntervalOption = function (animation_interval) {
|
||||
if (!Number.isInteger(animation_interval) && animation_interval >= 0) {
|
||||
throw new Error("The animation_interval must be a non-negative integer!");
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get an animation frame, have the agents update & get ready to be drawn, and keep doing that until paused or reset.
|
||||
*/
|
||||
Agentmap.prototype.run = function() {
|
||||
if (this.state.running === false) {
|
||||
this.state.running = true;
|
||||
Agentmap.prototype.run = function () {
|
||||
if (this.state.running === false) {
|
||||
this.state.running = true;
|
||||
|
||||
let animation_update = (function (rAF_time) {
|
||||
|
||||
if (this.state.paused === true) {
|
||||
this.state.paused = false;
|
||||
}
|
||||
this.state.animation_frame_id = L.Util.requestAnimFrame(animation_update);
|
||||
this.update();
|
||||
}).bind(this);
|
||||
let animation_update = function (rAF_time) {
|
||||
if (this.state.paused === true) {
|
||||
this.state.paused = false;
|
||||
}
|
||||
this.state.animation_frame_id = L.Util.requestAnimFrame(animation_update);
|
||||
this.update();
|
||||
}.bind(this);
|
||||
|
||||
this.state.animation_frame_id = L.Util.requestAnimFrame(animation_update);
|
||||
}
|
||||
}
|
||||
this.state.animation_frame_id = L.Util.requestAnimFrame(animation_update);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the simulation at the given time.
|
||||
* @private
|
||||
*/
|
||||
Agentmap.prototype.update = function() {
|
||||
if (this.state.ticks === null) {
|
||||
this.state.ticks = 0;
|
||||
}
|
||||
Agentmap.prototype.update = function () {
|
||||
if (this.state.ticks === null) {
|
||||
this.state.ticks = 0;
|
||||
}
|
||||
|
||||
//Execute user-provided per-tick instructions for the agentmap.
|
||||
this.controller();
|
||||
//Execute user-provided per-tick instructions for the agentmap.
|
||||
this.controller();
|
||||
|
||||
//Execute user-provided per-tick instructions for each agent.
|
||||
this.agents.eachLayer(function(agent) {
|
||||
agent.controller();
|
||||
});
|
||||
|
||||
this.state.ticks += 1;
|
||||
//Execute user-provided per-tick instructions for each agent.
|
||||
this.agents.eachLayer(function (agent) {
|
||||
agent.controller();
|
||||
});
|
||||
|
||||
this.state.ticks += 1;
|
||||
};
|
||||
|
||||
/**
|
||||
* Stop the animation, reset the animation state properties, and delete the features.
|
||||
*/
|
||||
Agentmap.prototype.clear = function() {
|
||||
L.Util.cancelAnimFrame(this.state.animation_frame_id);
|
||||
this.state.running = false,
|
||||
this.state.paused = false,
|
||||
this.state.animation_frame_id = null,
|
||||
this.state.ticks = null,
|
||||
|
||||
this.agents.clearLayers();
|
||||
this.streets.clearLayers();
|
||||
this.units.clearLayers();
|
||||
* Stop the animation, reset the animation state properties, and delete the features.
|
||||
*/
|
||||
Agentmap.prototype.clear = function () {
|
||||
L.Util.cancelAnimFrame(this.state.animation_frame_id);
|
||||
(this.state.running = false),
|
||||
(this.state.paused = false),
|
||||
(this.state.animation_frame_id = null),
|
||||
(this.state.ticks = null),
|
||||
this.agents.clearLayers();
|
||||
this.streets.clearLayers();
|
||||
this.units.clearLayers();
|
||||
};
|
||||
|
||||
/**
|
||||
/**
|
||||
* Stop the animation, stop updating the agents.
|
||||
*/
|
||||
Agentmap.prototype.pause = function() {
|
||||
L.Util.cancelAnimFrame(this.state.animation_frame_id);
|
||||
this.state.running = false,
|
||||
this.state.paused = true;
|
||||
Agentmap.prototype.pause = function () {
|
||||
L.Util.cancelAnimFrame(this.state.animation_frame_id);
|
||||
(this.state.running = false), (this.state.paused = true);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -135,19 +132,19 @@ Agentmap.prototype.pause = function() {
|
||||
* @param {number} unit_id - The unique ID of the unit whose door you want.
|
||||
* @returns {LatLng} - The coordinates of the center point of the segment of the unit parallel to the street.
|
||||
*/
|
||||
Agentmap.prototype.getUnitDoor = function(unit_id) {
|
||||
let unit = this.units.getLayer(unit_id);
|
||||
|
||||
if (typeof(unit) === "undefined") {
|
||||
throw new Error("No unit with the specified ID exists.");
|
||||
}
|
||||
|
||||
let unit_spec = unit.getLatLngs()[0],
|
||||
corner_a = unit_spec[0],
|
||||
corner_b = unit_spec[1],
|
||||
door = L.latLngBounds(corner_a, corner_b).getCenter();
|
||||
|
||||
return door;
|
||||
Agentmap.prototype.getUnitDoor = function (unit_id) {
|
||||
let unit = this.units.getLayer(unit_id);
|
||||
|
||||
if (typeof unit === "undefined") {
|
||||
throw new Error("No unit with the specified ID exists.");
|
||||
}
|
||||
|
||||
let unit_spec = unit.getLatLngs()[0],
|
||||
corner_a = unit_spec[0],
|
||||
corner_b = unit_spec[1],
|
||||
door = L.latLngBounds(corner_a, corner_b).getCenter();
|
||||
|
||||
return door;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -156,125 +153,127 @@ Agentmap.prototype.getUnitDoor = function(unit_id) {
|
||||
* @param {number} unit_id - The unique ID of the unit whose door's corresponding point on the street you want.
|
||||
* @returns {LatLng} - The coordinates point of the adjacent street directly in front of unit's door.
|
||||
*/
|
||||
Agentmap.prototype.getStreetNearDoor = function(unit_id) {
|
||||
let unit = this.units.getLayer(unit_id);
|
||||
|
||||
if (typeof(unit) === "undefined") {
|
||||
throw new Error("No unit with the specified ID exists.");
|
||||
}
|
||||
|
||||
let unit_anchors = L.A.reversedCoordinates(unit.street_anchors),
|
||||
street_point = L.latLngBounds(...unit_anchors).getCenter();
|
||||
|
||||
return street_point;
|
||||
Agentmap.prototype.getStreetNearDoor = function (unit_id) {
|
||||
let unit = this.units.getLayer(unit_id);
|
||||
|
||||
if (typeof unit === "undefined") {
|
||||
throw new Error("No unit with the specified ID exists.");
|
||||
}
|
||||
|
||||
let unit_anchors = L.A.reversedCoordinates(unit.street_anchors),
|
||||
street_point = L.latLngBounds(...unit_anchors).getCenter();
|
||||
|
||||
return street_point;
|
||||
};
|
||||
|
||||
/**
|
||||
* Given a unit and a pair of coordinates between 0 and 1, return a corresponding point inside the unit, offset from its first corner along the street.
|
||||
*
|
||||
*
|
||||
* @param {number} unit_id - The unique ID of the unit whose interior point you want.
|
||||
* @param {number} x - A point between 0 and 1 representing a position along the width of a unit.
|
||||
* @param {number} y - A point between 0 and 1 representing a position along the depth of a unit.
|
||||
* @returns {LatLng} - The global coordinates of the specified position within the unit.
|
||||
*/
|
||||
Agentmap.prototype.getUnitPoint = function(unit_id, x, y) {
|
||||
if (x < 0 || x > 1 || y < 0 || y > 1) {
|
||||
throw new Error("x and y must both be between 0 and 1!");
|
||||
}
|
||||
|
||||
let unit = this.units.getLayer(unit_id),
|
||||
unit_corners = unit.getLatLngs()[0],
|
||||
front_right = unit_corners[0],
|
||||
front_left = unit_corners[1],
|
||||
back_right = unit_corners[3],
|
||||
front_length = front_left.lng - front_right.lng,
|
||||
side_length = back_right.lng - front_right.lng,
|
||||
front_slope = (front_right.lat - front_left.lat) / (front_right.lng - front_left.lng),
|
||||
side_slope = (front_right.lat - back_right.lat) / (front_right.lng - back_right.lng);
|
||||
|
||||
//Get the coordinate of the position along the front (x) axis.
|
||||
let lng_along_front = front_right.lng + front_length * x,
|
||||
lat_along_front = front_right.lat + (front_length * x) * front_slope,
|
||||
point_along_front = L.latLng(lat_along_front, lng_along_front);
|
||||
|
||||
//From the position on the front axis, get the coordinate of a position along a line perpendicular to the front and
|
||||
//parallel to the side (y) axis.
|
||||
let lng_along_side = point_along_front.lng + side_length * y,
|
||||
lat_along_side = point_along_front.lat + (side_length * y) * side_slope,
|
||||
point_in_depth = L.latLng(lat_along_side, lng_along_side);
|
||||
Agentmap.prototype.getUnitPoint = function (unit_id, x, y) {
|
||||
if (x < 0 || x > 1 || y < 0 || y > 1) {
|
||||
throw new Error("x and y must both be between 0 and 1!");
|
||||
}
|
||||
|
||||
return point_in_depth;
|
||||
}
|
||||
let unit = this.units.getLayer(unit_id),
|
||||
unit_corners = unit.getLatLngs()[0],
|
||||
front_right = unit_corners[0],
|
||||
front_left = unit_corners[1],
|
||||
back_right = unit_corners[3],
|
||||
front_length = front_left.lng - front_right.lng,
|
||||
side_length = back_right.lng - front_right.lng,
|
||||
front_slope =
|
||||
(front_right.lat - front_left.lat) / (front_right.lng - front_left.lng),
|
||||
side_slope =
|
||||
(front_right.lat - back_right.lat) / (front_right.lng - back_right.lng);
|
||||
|
||||
//Get the coordinate of the position along the front (x) axis.
|
||||
let lng_along_front = front_right.lng + front_length * x,
|
||||
lat_along_front = front_right.lat + front_length * x * front_slope,
|
||||
point_along_front = L.latLng(lat_along_front, lng_along_front);
|
||||
|
||||
//From the position on the front axis, get the coordinate of a position along a line perpendicular to the front and
|
||||
//parallel to the side (y) axis.
|
||||
let lng_along_side = point_along_front.lng + side_length * y,
|
||||
lat_along_side = point_along_front.lat + side_length * y * side_slope,
|
||||
point_in_depth = L.latLng(lat_along_side, lng_along_side);
|
||||
|
||||
return point_in_depth;
|
||||
};
|
||||
|
||||
/**
|
||||
* Given a point on a street, find the nearest intersection on that street (with any other street).
|
||||
*
|
||||
*
|
||||
* @param {LatLng} lat_lng - The coordinates of the point on the street to search from.
|
||||
* @param {Place} place - A place object corresponding to the street.
|
||||
* @returns {LatLng} - The coordinates of the nearest intersection.
|
||||
*/
|
||||
Agentmap.prototype.getNearestIntersection = function(lat_lng, place) {
|
||||
let street_id,
|
||||
street_feature;
|
||||
Agentmap.prototype.getNearestIntersection = function (lat_lng, place) {
|
||||
let street_id, street_feature;
|
||||
|
||||
if (place.type === "street") {
|
||||
street_id = place.id;
|
||||
}
|
||||
else {
|
||||
throw new Error("place must be a street!");
|
||||
}
|
||||
if (place.type === "street") {
|
||||
street_id = place.id;
|
||||
} else {
|
||||
throw new Error("place must be a street!");
|
||||
}
|
||||
|
||||
street_feature = this.streets.getLayer(street_id).toGeoJSON();
|
||||
|
||||
let intersections = this.streets.getLayer(street_id).intersections,
|
||||
intersection_points = [],
|
||||
intersection_distances = [];
|
||||
street_feature = this.streets.getLayer(street_id).toGeoJSON();
|
||||
|
||||
for (let intersection in intersections) {
|
||||
for (let cross_point of intersections[intersection]) {
|
||||
let intersection_point = cross_point[0],
|
||||
distance = lat_lng.distanceTo(intersection_point);
|
||||
let intersections = this.streets.getLayer(street_id).intersections,
|
||||
intersection_points = [],
|
||||
intersection_distances = [];
|
||||
|
||||
/* More precise, but slower, distance detection -- not necessary yet.
|
||||
for (let intersection in intersections) {
|
||||
for (let cross_point of intersections[intersection]) {
|
||||
let intersection_point = cross_point[0],
|
||||
distance = lat_lng.distanceTo(intersection_point);
|
||||
|
||||
/* More precise, but slower, distance detection -- not necessary yet.
|
||||
let start_coords = L.A.pointToCoordinateArray(lat_lng);
|
||||
intersection_coords = L.A.pointToCoordinateArray(intersection_point),
|
||||
segment = lineSlice(start_coords, intersection_coords, street_feature),
|
||||
distance = length(segment);
|
||||
*/
|
||||
|
||||
intersection_points.push(intersection_point);
|
||||
intersection_distances.push(distance);
|
||||
}
|
||||
}
|
||||
|
||||
let smallest_distance = Math.min(...intersection_distances),
|
||||
smallest_distance_index = intersection_distances.indexOf(smallest_distance),
|
||||
closest_intersection_point = L.latLng(intersection_points[smallest_distance_index]);
|
||||
|
||||
return closest_intersection_point;
|
||||
}
|
||||
|
||||
intersection_points.push(intersection_point);
|
||||
intersection_distances.push(distance);
|
||||
}
|
||||
}
|
||||
|
||||
let smallest_distance = Math.min(...intersection_distances),
|
||||
smallest_distance_index = intersection_distances.indexOf(smallest_distance),
|
||||
closest_intersection_point = L.latLng(
|
||||
intersection_points[smallest_distance_index],
|
||||
);
|
||||
|
||||
return closest_intersection_point;
|
||||
};
|
||||
|
||||
/**
|
||||
* Since units may take a noticeably long time to generate while typically staying the same over simulations,
|
||||
* downloadUnits makes it easy to get a JS file containing the units object, so it can be included with an
|
||||
* AgentMaps app and imported into Agentmap.buildingify so they will not need to be regenerated.
|
||||
*/
|
||||
Agentmap.prototype.downloadUnits = function() {
|
||||
let file_content = "let units_data = ",
|
||||
units_json = this.units.toGeoJSON(20);
|
||||
file_content += JSON.stringify(units_json),
|
||||
file = new Blob([file_content]);
|
||||
Agentmap.prototype.downloadUnits = function () {
|
||||
let file_content = "let units_data = ",
|
||||
units_json = this.units.toGeoJSON(20);
|
||||
(file_content += JSON.stringify(units_json)),
|
||||
(file = new Blob([file_content]));
|
||||
|
||||
var element = document.createElement("a");
|
||||
element.setAttribute("href", URL.createObjectURL(file)),
|
||||
element.setAttribute("download", "units_data.js"),
|
||||
element.style.display = "none";
|
||||
document.body.appendChild(element);
|
||||
|
||||
element.click();
|
||||
|
||||
document.body.removeChild(element);
|
||||
}
|
||||
var element = document.createElement("a");
|
||||
element.setAttribute("href", URL.createObjectURL(file)),
|
||||
element.setAttribute("download", "units_data.js"),
|
||||
(element.style.display = "none");
|
||||
document.body.appendChild(element);
|
||||
|
||||
element.click();
|
||||
|
||||
document.body.removeChild(element);
|
||||
};
|
||||
|
||||
/**
|
||||
* Generates an agentmap for the given map.
|
||||
@ -284,7 +283,7 @@ Agentmap.prototype.downloadUnits = function() {
|
||||
* @returns {object} - An Agentmap instance.
|
||||
*/
|
||||
function agentmapFactory(map) {
|
||||
return new Agentmap(map);
|
||||
return new Agentmap(map);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -293,10 +292,9 @@ function agentmapFactory(map) {
|
||||
* @memberof L.LayerGroup
|
||||
*/
|
||||
function layerCount() {
|
||||
return this.getLayers().length;
|
||||
return this.getLayers().length;
|
||||
}
|
||||
|
||||
L.LayerGroup.include({count: layerCount});
|
||||
L.LayerGroup.include({ count: layerCount });
|
||||
|
||||
exports.Agentmap = Agentmap,
|
||||
exports.agentmap = agentmapFactory;
|
||||
(exports.Agentmap = Agentmap), (exports.agentmap = agentmapFactory);
|
||||
|
||||
@ -3,17 +3,17 @@
|
||||
/* Functions that help design and generate building units onto the map. */
|
||||
|
||||
let L = require("./featuretool").L,
|
||||
new_status = require("./featuretool").new_status,
|
||||
status_update = require("./featuretool").status_update,
|
||||
end_status = require("./featuretool").end_status,
|
||||
bearing = require('@turf/bearing').default,
|
||||
destination = require('@turf/destination').default,
|
||||
along = require('@turf/along').default,
|
||||
lineIntersect = require('@turf/line-intersect').default,
|
||||
intersect = require('@turf/intersect').default,
|
||||
Agentmap = require('./browserlessagentmap').Agentmap,
|
||||
streetsToGraph = require('./browserlessrouting').streetsToGraph,
|
||||
getPathFinder = require('./browserlessrouting').getPathFinder;
|
||||
new_status = require("./featuretool").new_status,
|
||||
status_update = require("./featuretool").status_update,
|
||||
end_status = require("./featuretool").end_status,
|
||||
bearing = require("@turf/bearing").default,
|
||||
destination = require("@turf/destination").default,
|
||||
along = require("@turf/along").default,
|
||||
lineIntersect = require("@turf/line-intersect").default,
|
||||
intersect = require("@turf/intersect").default,
|
||||
Agentmap = require("./browserlessagentmap").Agentmap,
|
||||
streetsToGraph = require("./browserlessrouting").streetsToGraph,
|
||||
getPathFinder = require("./browserlessrouting").getPathFinder;
|
||||
|
||||
/**
|
||||
* Generate and setup the desired map features (e.g. streets, houses).
|
||||
@ -31,11 +31,24 @@ getPathFinder = require('./browserlessrouting').getPathFinder;
|
||||
* @param {object} [unit_layers] - If you want to load a previously generated AgentMaps.units object instead of generating one from scarch: A GeoJSON Feature Collection of an AgentMaps.units featureGroup.
|
||||
* @param {object} [street_layers] - If you want to load a previously generated AgentMaps.streets object instead of generating one from scarch: A GeoJSON Feature Collection of an AgentMaps.streets featureGroup.
|
||||
*/
|
||||
function buildingify(bounding_box, OSM_data, street_options, unit_options, unit_layers, street_layers) {
|
||||
setupStreetFeatures.call(this, OSM_data, street_options);
|
||||
setupUnitFeatures.call(this, bounding_box, OSM_data, unit_options, unit_layers);
|
||||
|
||||
console.log("Finished!");
|
||||
function buildingify(
|
||||
bounding_box,
|
||||
OSM_data,
|
||||
street_options,
|
||||
unit_options,
|
||||
unit_layers,
|
||||
street_layers,
|
||||
) {
|
||||
setupStreetFeatures.call(this, OSM_data, street_options);
|
||||
setupUnitFeatures.call(
|
||||
this,
|
||||
bounding_box,
|
||||
OSM_data,
|
||||
unit_options,
|
||||
unit_layers,
|
||||
);
|
||||
|
||||
console.log("Finished!");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -46,58 +59,55 @@ function buildingify(bounding_box, OSM_data, street_options, unit_options, unit_
|
||||
* @param {object} [street_layers] - If you want to load a previously generated AgentMaps.streets object instead of generating one from scarch: A GeoJSON Feature Collection of an AgentMaps.streets featureGroup.
|
||||
*/
|
||||
function setupStreetFeatures(OSM_data, street_options, street_layers) {
|
||||
console.log("Setting up streets...");
|
||||
let default_options = {
|
||||
"color": "yellow",
|
||||
"weight": 4,
|
||||
"opacity": .5
|
||||
};
|
||||
console.log("Setting up streets...");
|
||||
let default_options = {
|
||||
color: "yellow",
|
||||
weight: 4,
|
||||
opacity: 0.5,
|
||||
};
|
||||
|
||||
street_options = Object.assign(default_options, street_options);
|
||||
street_options = Object.assign(default_options, street_options);
|
||||
|
||||
let street_feature_collection;
|
||||
let street_feature_collection;
|
||||
|
||||
if (typeof(street_layers) === "undefined") {
|
||||
let street_features = getStreetFeatures(OSM_data);
|
||||
|
||||
street_feature_collection = {
|
||||
type: "FeatureCollection",
|
||||
features: street_features
|
||||
};
|
||||
}
|
||||
else {
|
||||
street_feature_collection = street_layers;
|
||||
}
|
||||
|
||||
this.streets = L.geoJSON(
|
||||
street_feature_collection,
|
||||
street_options
|
||||
);
|
||||
|
||||
//Map streets' OSM IDs to their Leaflet IDs.
|
||||
this.streets.id_map = {};
|
||||
if (typeof street_layers === "undefined") {
|
||||
let street_features = getStreetFeatures(OSM_data);
|
||||
|
||||
//Having added the streets as layers to the map, do any processing that requires access to those layers.
|
||||
new_status()
|
||||
let i = 1;
|
||||
this.streets.eachLayer(function(street) {
|
||||
status_update("Relating " + i + " of " + this.streets.count() + " streets...");
|
||||
this.streets.id_map[street.feature.id] = street._leaflet_id;
|
||||
street_feature_collection = {
|
||||
type: "FeatureCollection",
|
||||
features: street_features,
|
||||
};
|
||||
} else {
|
||||
street_feature_collection = street_layers;
|
||||
}
|
||||
|
||||
addStreetLayerIntersections.call(this, street);
|
||||
this.streets = L.geoJSON(street_feature_collection, street_options);
|
||||
|
||||
i++;
|
||||
}, this);
|
||||
|
||||
end_status();
|
||||
//Map streets' OSM IDs to their Leaflet IDs.
|
||||
this.streets.id_map = {};
|
||||
|
||||
//Add general graph-making and path-finder-making methods to Agentmap, in case streets are added, removed, or modified mid-simulation.
|
||||
console.log("Generating street graph...");
|
||||
this.streetsToGraph = streetsToGraph,
|
||||
this.getPathFinder = getPathFinder;
|
||||
|
||||
this.streets.graph = streetsToGraph(this.streets),
|
||||
this.pathfinder = getPathFinder(this.streets.graph);
|
||||
//Having added the streets as layers to the map, do any processing that requires access to those layers.
|
||||
new_status();
|
||||
let i = 1;
|
||||
this.streets.eachLayer(function (street) {
|
||||
status_update(
|
||||
"Relating " + i + " of " + this.streets.count() + " streets...",
|
||||
);
|
||||
this.streets.id_map[street.feature.id] = street._leaflet_id;
|
||||
|
||||
addStreetLayerIntersections.call(this, street);
|
||||
|
||||
i++;
|
||||
}, this);
|
||||
|
||||
end_status();
|
||||
|
||||
//Add general graph-making and path-finder-making methods to Agentmap, in case streets are added, removed, or modified mid-simulation.
|
||||
console.log("Generating street graph...");
|
||||
(this.streetsToGraph = streetsToGraph), (this.getPathFinder = getPathFinder);
|
||||
|
||||
(this.streets.graph = streetsToGraph(this.streets)),
|
||||
(this.pathfinder = getPathFinder(this.streets.graph));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -108,51 +118,64 @@ function setupStreetFeatures(OSM_data, street_options, street_layers) {
|
||||
* @returns {Array<Feature>} - array of street features.
|
||||
*/
|
||||
function getStreetFeatures(OSM_data) {
|
||||
let street_features = [];
|
||||
let street_features = [];
|
||||
|
||||
for (let i = 0; i < OSM_data.features.length; ++i) {
|
||||
let feature = OSM_data.features[i];
|
||||
for (let i = 0; i < OSM_data.features.length; ++i) {
|
||||
let feature = OSM_data.features[i];
|
||||
|
||||
if (feature.geometry.type === "LineString" && feature.properties.highway) {
|
||||
let street_feature = feature;
|
||||
if (feature.geometry.type === "LineString" && feature.properties.highway) {
|
||||
let street_feature = feature;
|
||||
|
||||
street_features.push(street_feature);
|
||||
}
|
||||
}
|
||||
street_features.push(street_feature);
|
||||
}
|
||||
}
|
||||
|
||||
return street_features;
|
||||
return street_features;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the intersections of all the streets on the map and adds them as properties to the street layers.
|
||||
* @private
|
||||
*
|
||||
*
|
||||
* @param {object} street - A Leaflet polyline representing a street.
|
||||
*/
|
||||
function addStreetLayerIntersections(street) {
|
||||
let street_id = street._leaflet_id;
|
||||
let street_id = street._leaflet_id;
|
||||
|
||||
street.intersections = typeof(street.intersections) === "undefined" ? {} : street.intersections;
|
||||
street.intersections =
|
||||
typeof street.intersections === "undefined" ? {} : street.intersections;
|
||||
|
||||
this.streets.eachLayer(function(other_street) {
|
||||
let other_street_id = other_street._leaflet_id;
|
||||
this.streets.eachLayer(function (other_street) {
|
||||
let other_street_id = other_street._leaflet_id;
|
||||
|
||||
//Skip if both streets are the same, or if the street already has its intersections with the other street.
|
||||
if (typeof(street.intersections[other_street_id]) === "undefined" && street_id !== other_street_id) {
|
||||
let street_coords = street.getLatLngs().map(L.A.pointToCoordinateArray),
|
||||
other_street_coords = other_street.getLatLngs().map(L.A.pointToCoordinateArray),
|
||||
identified_intersections = L.A.getIntersections(street_coords, other_street_coords, [street_id, other_street_id]).map(
|
||||
identified_intersection =>
|
||||
[L.latLng(L.A.reversedCoordinates(identified_intersection[0])), identified_intersection[1]]
|
||||
);
|
||||
//Skip if both streets are the same, or if the street already has its intersections with the other street.
|
||||
if (
|
||||
typeof street.intersections[other_street_id] === "undefined" &&
|
||||
street_id !== other_street_id
|
||||
) {
|
||||
let street_coords = street.getLatLngs().map(L.A.pointToCoordinateArray),
|
||||
other_street_coords = other_street
|
||||
.getLatLngs()
|
||||
.map(L.A.pointToCoordinateArray),
|
||||
identified_intersections = L.A.getIntersections(
|
||||
street_coords,
|
||||
other_street_coords,
|
||||
[street_id, other_street_id],
|
||||
).map((identified_intersection) => [
|
||||
L.latLng(L.A.reversedCoordinates(identified_intersection[0])),
|
||||
identified_intersection[1],
|
||||
]);
|
||||
|
||||
if (identified_intersections.length > 0) {
|
||||
street.intersections[other_street_id] = identified_intersections,
|
||||
other_street.intersections = typeof(other_street.intersections) === "undefined" ? {} : other_street.intersections,
|
||||
other_street.intersections[street_id] = identified_intersections;
|
||||
}
|
||||
}
|
||||
});
|
||||
if (identified_intersections.length > 0) {
|
||||
(street.intersections[other_street_id] = identified_intersections),
|
||||
(other_street.intersections =
|
||||
typeof other_street.intersections === "undefined"
|
||||
? {}
|
||||
: other_street.intersections),
|
||||
(other_street.intersections[street_id] = identified_intersections);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -163,63 +186,76 @@ function addStreetLayerIntersections(street) {
|
||||
* @param {object} unit_options - An object containing the Leaflet & AgentMaps styling options for units.
|
||||
* @param {object} [unit_layers] - If you want to load a previously generated AgentMaps.units object instead of generating one from scarch: A GeoJSON Feature Collection of an AgentMaps.units featureGroup.
|
||||
*/
|
||||
function setupUnitFeatures(bounding_box, OSM_data, unit_options = {}, unit_layers) {
|
||||
console.log("Setting up units...");
|
||||
function setupUnitFeatures(
|
||||
bounding_box,
|
||||
OSM_data,
|
||||
unit_options = {},
|
||||
unit_layers,
|
||||
) {
|
||||
console.log("Setting up units...");
|
||||
|
||||
let default_options = {
|
||||
"color": "green",
|
||||
"weight": 1,
|
||||
"opacity": .87,
|
||||
"front_buffer": 6,
|
||||
"side_buffer": 3,
|
||||
"length": 14,
|
||||
"depth": 18
|
||||
};
|
||||
let default_options = {
|
||||
color: "green",
|
||||
weight: 1,
|
||||
opacity: 0.87,
|
||||
front_buffer: 6,
|
||||
side_buffer: 3,
|
||||
length: 14,
|
||||
depth: 18,
|
||||
};
|
||||
|
||||
unit_options = Object.assign(default_options, unit_options);
|
||||
|
||||
let unit_feature_collection;
|
||||
unit_options = Object.assign(default_options, unit_options);
|
||||
|
||||
//If no unit_layers is supplied, generate the units from scratch.
|
||||
if (typeof(unit_layers) === "undefined") {
|
||||
//Bind getUnitFeatures to "this" so it can access the agentmap as "this.agentmap".
|
||||
let unit_features = getUnitFeatures.bind(this)(bounding_box, OSM_data, unit_options);
|
||||
let unit_feature_collection;
|
||||
|
||||
unit_feature_collection = {
|
||||
type: "FeatureCollection",
|
||||
features: unit_features
|
||||
};
|
||||
}
|
||||
else {
|
||||
unit_feature_collection = unit_layers;
|
||||
}
|
||||
|
||||
console.log("Loading units...");
|
||||
this.units = L.geoJSON(
|
||||
unit_feature_collection,
|
||||
unit_options
|
||||
);
|
||||
|
||||
new_status();
|
||||
let i = 1;
|
||||
//Having added the units as layers to the map, do any processing that requires access to those layers.
|
||||
this.units.eachLayer(function(unit) {
|
||||
if (typeof(unit_layers) === "undefined") {
|
||||
unit.street_id = unit.feature.properties.street_id;
|
||||
}
|
||||
else {
|
||||
unit.street_id = this.streets.id_map[unit.feature.properties.OSM_street_id];
|
||||
}
|
||||
//If no unit_layers is supplied, generate the units from scratch.
|
||||
if (typeof unit_layers === "undefined") {
|
||||
//Bind getUnitFeatures to "this" so it can access the agentmap as "this.agentmap".
|
||||
let unit_features = getUnitFeatures.bind(this)(
|
||||
bounding_box,
|
||||
OSM_data,
|
||||
unit_options,
|
||||
);
|
||||
|
||||
unit.street_anchors = unit.feature.properties.street_anchors,
|
||||
|
||||
status_update("Finding neighbors for unit " + i + " of " + this.units.count() + " units.");
|
||||
//Change the IDs of each unit in this unit's neighbours array into the appropriate Leaflet IDs.
|
||||
unit.neighbors = getUnitNeighborLayerIDs.call(this, unit.feature.properties.neighbors);
|
||||
i++;
|
||||
}, this);
|
||||
unit_feature_collection = {
|
||||
type: "FeatureCollection",
|
||||
features: unit_features,
|
||||
};
|
||||
} else {
|
||||
unit_feature_collection = unit_layers;
|
||||
}
|
||||
|
||||
end_status();
|
||||
console.log("Loading units...");
|
||||
this.units = L.geoJSON(unit_feature_collection, unit_options);
|
||||
|
||||
new_status();
|
||||
let i = 1;
|
||||
//Having added the units as layers to the map, do any processing that requires access to those layers.
|
||||
this.units.eachLayer(function (unit) {
|
||||
if (typeof unit_layers === "undefined") {
|
||||
unit.street_id = unit.feature.properties.street_id;
|
||||
} else {
|
||||
unit.street_id =
|
||||
this.streets.id_map[unit.feature.properties.OSM_street_id];
|
||||
}
|
||||
|
||||
(unit.street_anchors = unit.feature.properties.street_anchors),
|
||||
status_update(
|
||||
"Finding neighbors for unit " +
|
||||
i +
|
||||
" of " +
|
||||
this.units.count() +
|
||||
" units.",
|
||||
);
|
||||
//Change the IDs of each unit in this unit's neighbours array into the appropriate Leaflet IDs.
|
||||
unit.neighbors = getUnitNeighborLayerIDs.call(
|
||||
this,
|
||||
unit.feature.properties.neighbors,
|
||||
);
|
||||
i++;
|
||||
}, this);
|
||||
|
||||
end_status();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -231,24 +267,23 @@ function setupUnitFeatures(bounding_box, OSM_data, unit_options = {}, unit_layer
|
||||
* @returns {Array<?number>} - An array of Leaflet layer IDs corresponding to the unit's neighbors.
|
||||
*/
|
||||
function getUnitNeighborLayerIDs(neighbors) {
|
||||
let neighbor_layer_ids = neighbors.map(function(neighbor) {
|
||||
if (neighbor !== null) {
|
||||
let neighbor_layer_id = null;
|
||||
|
||||
this.units.eachLayer(function(possible_neighbor_layer) {
|
||||
if (possible_neighbor_layer.feature.properties.id === neighbor) {
|
||||
neighbor_layer_id = this.units.getLayerId(possible_neighbor_layer);
|
||||
}
|
||||
}, this);
|
||||
let neighbor_layer_ids = neighbors.map(function (neighbor) {
|
||||
if (neighbor !== null) {
|
||||
let neighbor_layer_id = null;
|
||||
|
||||
return neighbor_layer_id;
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}, this);
|
||||
this.units.eachLayer(function (possible_neighbor_layer) {
|
||||
if (possible_neighbor_layer.feature.properties.id === neighbor) {
|
||||
neighbor_layer_id = this.units.getLayerId(possible_neighbor_layer);
|
||||
}
|
||||
}, this);
|
||||
|
||||
return neighbor_layer_ids;
|
||||
return neighbor_layer_id;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}, this);
|
||||
|
||||
return neighbor_layer_ids;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -261,32 +296,48 @@ function getUnitNeighborLayerIDs(neighbors) {
|
||||
* @returns {Array<Feature>} - array of features representing real estate units.
|
||||
*/
|
||||
function getUnitFeatures(bounding_box, OSM_data, unit_options) {
|
||||
let proposed_unit_features = [];
|
||||
|
||||
new_status();
|
||||
let i = 1;
|
||||
this.streets.eachLayer(function(layer) {
|
||||
status_update("Generating units for street " + i + " of " + this.streets.count() + " streets...");
|
||||
|
||||
let street_feature = layer.feature,
|
||||
street_id = layer._leaflet_id,
|
||||
street_OSM_id = layer.feature.id,
|
||||
proposed_anchors = getUnitAnchors(street_feature, bounding_box, unit_options),
|
||||
new_proposed_unit_features = generateUnitFeatures(proposed_anchors, proposed_unit_features, street_id, street_OSM_id, unit_options);
|
||||
proposed_unit_features.push(...new_proposed_unit_features);
|
||||
|
||||
i++;
|
||||
}, this);
|
||||
let proposed_unit_features = [];
|
||||
|
||||
end_status();
|
||||
|
||||
unit_features = unitsOutOfStreets(proposed_unit_features, this.streets);
|
||||
|
||||
return unit_features;
|
||||
new_status();
|
||||
let i = 1;
|
||||
this.streets.eachLayer(function (layer) {
|
||||
status_update(
|
||||
"Generating units for street " +
|
||||
i +
|
||||
" of " +
|
||||
this.streets.count() +
|
||||
" streets...",
|
||||
);
|
||||
|
||||
let street_feature = layer.feature,
|
||||
street_id = layer._leaflet_id,
|
||||
street_OSM_id = layer.feature.id,
|
||||
proposed_anchors = getUnitAnchors(
|
||||
street_feature,
|
||||
bounding_box,
|
||||
unit_options,
|
||||
),
|
||||
new_proposed_unit_features = generateUnitFeatures(
|
||||
proposed_anchors,
|
||||
proposed_unit_features,
|
||||
street_id,
|
||||
street_OSM_id,
|
||||
unit_options,
|
||||
);
|
||||
proposed_unit_features.push(...new_proposed_unit_features);
|
||||
|
||||
i++;
|
||||
}, this);
|
||||
|
||||
end_status();
|
||||
|
||||
unit_features = unitsOutOfStreets(proposed_unit_features, this.streets);
|
||||
|
||||
return unit_features;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an array of anchor pairs, for each anchor pair find four
|
||||
* Given an array of anchor pairs, for each anchor pair find four
|
||||
* nearby points on either side of the street appropriate to build a unit(s) on.
|
||||
* @private
|
||||
*
|
||||
@ -297,126 +348,162 @@ function getUnitFeatures(bounding_box, OSM_data, unit_options) {
|
||||
* @param {object} unit_options - An object containing the AgentMaps styling options for units.
|
||||
* @returns {Array<Feature>} unit_features - Array of features representing units.
|
||||
*/
|
||||
function generateUnitFeatures(unit_anchors, proposed_unit_features, street_leaflet_id, street_OSM_id, unit_options) {
|
||||
//One sub-array of unit features for each side of the road.
|
||||
let unit_features = [[],[]],
|
||||
starting_id = proposed_unit_features.length,
|
||||
increment = 1;
|
||||
|
||||
for (let anchor_pair of unit_anchors) {
|
||||
//Pair of unit_features opposite each other on a street.
|
||||
let unit_pair = [null, null];
|
||||
|
||||
for (let i of [1, -1]) {
|
||||
let anchor_a = anchor_pair[0].geometry.coordinates,
|
||||
anchor_b = anchor_pair[1].geometry.coordinates,
|
||||
anchor_latLng_pair = [anchor_a, anchor_b],
|
||||
street_buffer = unit_options.front_buffer / 1000, //Distance between center of street and start of unit.
|
||||
house_depth = unit_options.depth / 1000,
|
||||
angle = bearing(anchor_a, anchor_b),
|
||||
new_angle = angle + i * 90, //Angle of line perpendicular to the anchor segment.
|
||||
unit_feature = {
|
||||
type: "Feature",
|
||||
properties: {
|
||||
street: "none"
|
||||
},
|
||||
geometry: {
|
||||
type: "Polygon",
|
||||
coordinates: [[]]
|
||||
}
|
||||
};
|
||||
unit_feature.geometry.coordinates[0][0] = destination(anchor_a, street_buffer, new_angle).geometry.coordinates,
|
||||
unit_feature.geometry.coordinates[0][1] = destination(anchor_b, street_buffer, new_angle).geometry.coordinates,
|
||||
unit_feature.geometry.coordinates[0][2] = destination(anchor_b, street_buffer + house_depth, new_angle).geometry.coordinates,
|
||||
unit_feature.geometry.coordinates[0][3] = destination(anchor_a, street_buffer + house_depth, new_angle).geometry.coordinates;
|
||||
unit_feature.geometry.coordinates[0][4] = unit_feature.geometry.coordinates[0][0];
|
||||
function generateUnitFeatures(
|
||||
unit_anchors,
|
||||
proposed_unit_features,
|
||||
street_leaflet_id,
|
||||
street_OSM_id,
|
||||
unit_options,
|
||||
) {
|
||||
//One sub-array of unit features for each side of the road.
|
||||
let unit_features = [[], []],
|
||||
starting_id = proposed_unit_features.length,
|
||||
increment = 1;
|
||||
|
||||
//Exclude the unit if it overlaps with any of the other proposed units.
|
||||
let all_proposed_unit_features = unit_features[0].concat(unit_features[1]).concat(proposed_unit_features);
|
||||
if (noOverlaps(unit_feature, all_proposed_unit_features)) {
|
||||
//Recode index so that it's useful here.
|
||||
i = i === 1 ? 0 : 1;
|
||||
for (let anchor_pair of unit_anchors) {
|
||||
//Pair of unit_features opposite each other on a street.
|
||||
let unit_pair = [null, null];
|
||||
|
||||
unit_feature.properties.street_id = street_leaflet_id,
|
||||
unit_feature.properties.OSM_street_id = street_OSM_id,
|
||||
unit_feature.properties.street_anchors = anchor_latLng_pair,
|
||||
unit_feature.properties.neighbors = [null, null, null],
|
||||
unit_feature.properties.id = starting_id + increment,
|
||||
increment += 1;
|
||||
|
||||
if (unit_features[i].length !== 0) {
|
||||
//Make previous unit_feature this unit_feature's first neighbor.
|
||||
unit_feature.properties.neighbors[0] = unit_features[i][unit_features[i].length - 1].properties.id,
|
||||
//Make this unit_feature the previous unit_feature's second neighbor.
|
||||
unit_features[i][unit_features[i].length - 1].properties.neighbors[1] = unit_feature.properties.id;
|
||||
}
|
||||
|
||||
if (i === 0) {
|
||||
unit_pair[0] = unit_feature;
|
||||
}
|
||||
else {
|
||||
if (unit_pair[0] !== null) {
|
||||
//Make unit_feature opposite to this unit_feature on the street its third neighbor.
|
||||
unit_feature.properties.neighbors[2] = unit_pair[0].properties.id,
|
||||
//Make unit_feature opposite to this unit_feature on the street's third neighbor this unit_feature.
|
||||
unit_pair[0].properties.neighbors[2] = unit_feature.properties.id;
|
||||
}
|
||||
|
||||
unit_pair[1] = unit_feature;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (unit_pair[0] !== null) {
|
||||
unit_features[0].push(unit_pair[0]);
|
||||
}
|
||||
for (let i of [1, -1]) {
|
||||
let anchor_a = anchor_pair[0].geometry.coordinates,
|
||||
anchor_b = anchor_pair[1].geometry.coordinates,
|
||||
anchor_latLng_pair = [anchor_a, anchor_b],
|
||||
street_buffer = unit_options.front_buffer / 1000, //Distance between center of street and start of unit.
|
||||
house_depth = unit_options.depth / 1000,
|
||||
angle = bearing(anchor_a, anchor_b),
|
||||
new_angle = angle + i * 90, //Angle of line perpendicular to the anchor segment.
|
||||
unit_feature = {
|
||||
type: "Feature",
|
||||
properties: {
|
||||
street: "none",
|
||||
},
|
||||
geometry: {
|
||||
type: "Polygon",
|
||||
coordinates: [[]],
|
||||
},
|
||||
};
|
||||
(unit_feature.geometry.coordinates[0][0] = destination(
|
||||
anchor_a,
|
||||
street_buffer,
|
||||
new_angle,
|
||||
).geometry.coordinates),
|
||||
(unit_feature.geometry.coordinates[0][1] = destination(
|
||||
anchor_b,
|
||||
street_buffer,
|
||||
new_angle,
|
||||
).geometry.coordinates),
|
||||
(unit_feature.geometry.coordinates[0][2] = destination(
|
||||
anchor_b,
|
||||
street_buffer + house_depth,
|
||||
new_angle,
|
||||
).geometry.coordinates),
|
||||
(unit_feature.geometry.coordinates[0][3] = destination(
|
||||
anchor_a,
|
||||
street_buffer + house_depth,
|
||||
new_angle,
|
||||
).geometry.coordinates);
|
||||
unit_feature.geometry.coordinates[0][4] =
|
||||
unit_feature.geometry.coordinates[0][0];
|
||||
|
||||
if (unit_pair[1] !== null) {
|
||||
unit_features[1].push(unit_pair[1]);
|
||||
}
|
||||
}
|
||||
//Exclude the unit if it overlaps with any of the other proposed units.
|
||||
let all_proposed_unit_features = unit_features[0]
|
||||
.concat(unit_features[1])
|
||||
.concat(proposed_unit_features);
|
||||
if (noOverlaps(unit_feature, all_proposed_unit_features)) {
|
||||
//Recode index so that it's useful here.
|
||||
i = i === 1 ? 0 : 1;
|
||||
|
||||
let unit_features_merged = [].concat(...unit_features);
|
||||
|
||||
return unit_features_merged;
|
||||
(unit_feature.properties.street_id = street_leaflet_id),
|
||||
(unit_feature.properties.OSM_street_id = street_OSM_id),
|
||||
(unit_feature.properties.street_anchors = anchor_latLng_pair),
|
||||
(unit_feature.properties.neighbors = [null, null, null]),
|
||||
(unit_feature.properties.id = starting_id + increment),
|
||||
(increment += 1);
|
||||
|
||||
if (unit_features[i].length !== 0) {
|
||||
//Make previous unit_feature this unit_feature's first neighbor.
|
||||
(unit_feature.properties.neighbors[0] =
|
||||
unit_features[i][unit_features[i].length - 1].properties.id),
|
||||
//Make this unit_feature the previous unit_feature's second neighbor.
|
||||
(unit_features[i][
|
||||
unit_features[i].length - 1
|
||||
].properties.neighbors[1] = unit_feature.properties.id);
|
||||
}
|
||||
|
||||
if (i === 0) {
|
||||
unit_pair[0] = unit_feature;
|
||||
} else {
|
||||
if (unit_pair[0] !== null) {
|
||||
//Make unit_feature opposite to this unit_feature on the street its third neighbor.
|
||||
(unit_feature.properties.neighbors[2] = unit_pair[0].properties.id),
|
||||
//Make unit_feature opposite to this unit_feature on the street's third neighbor this unit_feature.
|
||||
(unit_pair[0].properties.neighbors[2] =
|
||||
unit_feature.properties.id);
|
||||
}
|
||||
|
||||
unit_pair[1] = unit_feature;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (unit_pair[0] !== null) {
|
||||
unit_features[0].push(unit_pair[0]);
|
||||
}
|
||||
|
||||
if (unit_pair[1] !== null) {
|
||||
unit_features[1].push(unit_pair[1]);
|
||||
}
|
||||
}
|
||||
|
||||
let unit_features_merged = [].concat(...unit_features);
|
||||
|
||||
return unit_features_merged;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find anchors for potential units. chors are the pairs of start
|
||||
* Find anchors for potential units. chors are the pairs of start
|
||||
* and end points along the street from which units may be constructed.
|
||||
* @private
|
||||
*
|
||||
*
|
||||
* @param {Feature} street_feature - A GeoJSON feature object representing a street.
|
||||
* @param {object} unit_options - An object containing the AgentMaps styling options for units.
|
||||
* @returns {Array<Array<Feature>>} - Array of pairs of points around which to anchor units along a street.
|
||||
* @returns {Array<Array<Feature>>} - Array of pairs of points around which to anchor units along a street.
|
||||
*/
|
||||
function getUnitAnchors(street_feature, bounding_box, unit_options) {
|
||||
let unit_anchors = [],
|
||||
unit_length = unit_options.length / 1000, //Kilometers.
|
||||
unit_buffer = unit_options.side_buffer / 1000, //Distance between units, kilometers.
|
||||
endpoint = street_feature.geometry.coordinates[street_feature.geometry.coordinates.length - 1],
|
||||
start_anchor = along(street_feature, 0),
|
||||
end_anchor = along(street_feature, unit_length),
|
||||
distance_along = unit_length;
|
||||
|
||||
while (end_anchor.geometry.coordinates != endpoint) {
|
||||
//Exclude proposed anchors if they're outside of the bounding box.
|
||||
start_coord = L.A.reversedCoordinates(start_anchor.geometry.coordinates),
|
||||
end_coord = L.A.reversedCoordinates(end_anchor.geometry.coordinates);
|
||||
|
||||
if (L.latLngBounds(bounding_box).contains(start_coord) &&
|
||||
L.latLngBounds(bounding_box).contains(end_coord)) {
|
||||
unit_anchors.push([start_anchor, end_anchor]);
|
||||
}
|
||||
let unit_anchors = [],
|
||||
unit_length = unit_options.length / 1000, //Kilometers.
|
||||
unit_buffer = unit_options.side_buffer / 1000, //Distance between units, kilometers.
|
||||
endpoint =
|
||||
street_feature.geometry.coordinates[
|
||||
street_feature.geometry.coordinates.length - 1
|
||||
],
|
||||
start_anchor = along(street_feature, 0),
|
||||
end_anchor = along(street_feature, unit_length),
|
||||
distance_along = unit_length;
|
||||
|
||||
//Find next pair of anchors.
|
||||
start_anchor = along(street_feature, distance_along + unit_buffer);
|
||||
end_anchor = along(street_feature, distance_along + unit_buffer + unit_length);
|
||||
|
||||
distance_along += unit_buffer + unit_length;
|
||||
}
|
||||
while (end_anchor.geometry.coordinates != endpoint) {
|
||||
//Exclude proposed anchors if they're outside of the bounding box.
|
||||
(start_coord = L.A.reversedCoordinates(start_anchor.geometry.coordinates)),
|
||||
(end_coord = L.A.reversedCoordinates(end_anchor.geometry.coordinates));
|
||||
|
||||
return unit_anchors;
|
||||
if (
|
||||
L.latLngBounds(bounding_box).contains(start_coord) &&
|
||||
L.latLngBounds(bounding_box).contains(end_coord)
|
||||
) {
|
||||
unit_anchors.push([start_anchor, end_anchor]);
|
||||
}
|
||||
|
||||
//Find next pair of anchors.
|
||||
start_anchor = along(street_feature, distance_along + unit_buffer);
|
||||
end_anchor = along(
|
||||
street_feature,
|
||||
distance_along + unit_buffer + unit_length,
|
||||
);
|
||||
|
||||
distance_along += unit_buffer + unit_length;
|
||||
}
|
||||
|
||||
return unit_anchors;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -428,28 +515,41 @@ function getUnitAnchors(street_feature, bounding_box, unit_options) {
|
||||
* @returns {Array<Feature>} - unit_features, but with all units that intersect any streets removed.
|
||||
*/
|
||||
function unitsOutOfStreets(unit_features, street_layers) {
|
||||
let processed_unit_features = unit_features.slice();
|
||||
|
||||
new_status();
|
||||
let i = 1;
|
||||
street_layers.eachLayer(function(street_layer) {
|
||||
status_update("Removing superfluous units from streets. Checking street " + i + " of " + street_layers.count() + "...");
|
||||
|
||||
let street_feature = street_layer.feature;
|
||||
for (let unit_feature of processed_unit_features) {
|
||||
let intersection_exists = lineIntersect(street_feature, unit_feature).features.length > 0;
|
||||
if (intersection_exists) {
|
||||
processed_unit_features.splice(processed_unit_features.indexOf(unit_feature), 1, null);
|
||||
}
|
||||
}
|
||||
let processed_unit_features = unit_features.slice();
|
||||
|
||||
processed_unit_features = processed_unit_features.filter(feature => feature === null ? false : true);
|
||||
i++;
|
||||
});
|
||||
|
||||
end_status();
|
||||
new_status();
|
||||
let i = 1;
|
||||
street_layers.eachLayer(function (street_layer) {
|
||||
status_update(
|
||||
"Removing superfluous units from streets. Checking street " +
|
||||
i +
|
||||
" of " +
|
||||
street_layers.count() +
|
||||
"...",
|
||||
);
|
||||
|
||||
return processed_unit_features;
|
||||
let street_feature = street_layer.feature;
|
||||
for (let unit_feature of processed_unit_features) {
|
||||
let intersection_exists =
|
||||
lineIntersect(street_feature, unit_feature).features.length > 0;
|
||||
if (intersection_exists) {
|
||||
processed_unit_features.splice(
|
||||
processed_unit_features.indexOf(unit_feature),
|
||||
1,
|
||||
null,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
processed_unit_features = processed_unit_features.filter((feature) =>
|
||||
feature === null ? false : true,
|
||||
);
|
||||
i++;
|
||||
});
|
||||
|
||||
end_status();
|
||||
|
||||
return processed_unit_features;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -459,16 +559,19 @@ function unitsOutOfStreets(unit_features, street_layers) {
|
||||
* @param {Feature} reference_polygon_feature - A geoJSON polygon feature.
|
||||
* @param {Array<Feature>} polygon_feature_array - Array of geoJSON polygon features.
|
||||
* @returns {boolean} - Whether the polygon_feature overlaps with any one in the array.
|
||||
*/
|
||||
*/
|
||||
function noOverlaps(reference_polygon_feature, polygon_feature_array) {
|
||||
for (feature_array_element of polygon_feature_array) {
|
||||
let overlap_exists = intersect(reference_polygon_feature, feature_array_element);
|
||||
if (overlap_exists) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (feature_array_element of polygon_feature_array) {
|
||||
let overlap_exists = intersect(
|
||||
reference_polygon_feature,
|
||||
feature_array_element,
|
||||
);
|
||||
if (overlap_exists) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
|
||||
Agentmap.prototype.buildingify = buildingify;
|
||||
|
||||
@ -3,29 +3,29 @@
|
||||
/* Here we have utilities to convert OSM geojson data into a distance-weighted graph and find the shortest path between two points. */
|
||||
|
||||
let L = require("./featuretool").L,
|
||||
path = require("ngraph.path"),
|
||||
createGraph = require("ngraph.graph"),
|
||||
lineSlice = require('@turf/line-slice').default,
|
||||
length = require('@turf/length').default,
|
||||
Agentmap = require('./browserlessagentmap').Agentmap;
|
||||
path = require("ngraph.path"),
|
||||
createGraph = require("ngraph.graph"),
|
||||
lineSlice = require("@turf/line-slice").default,
|
||||
length = require("@turf/length").default,
|
||||
Agentmap = require("./browserlessagentmap").Agentmap;
|
||||
|
||||
/**
|
||||
* Convert a layerGroup of streets into a graph. Useful if you modify the street layers during the simulation
|
||||
* and want routing to work with the new street network.
|
||||
*
|
||||
* @param {LayerGroup} streets - A Leaflet layerGroup of streets, forming a street network.
|
||||
* @returns {Object} - A graph representing the street network, operable by the ngraph pathfinder.
|
||||
* @returns {Object} - A graph representing the street network, operable by the ngraph pathfinder.
|
||||
*/
|
||||
function streetsToGraph(streets) {
|
||||
let graph = createGraph(),
|
||||
streetToGraphBound = streetToGraph.bind(this, graph);
|
||||
|
||||
//For each street, get an array of indices for the start, intersections, and end coordinates, in order from
|
||||
//start to end. Then, add the coordinates at each index as a node, and an edge between each adjacent node in the array,
|
||||
//associating the distance between the nodes (between their coordinates) with each edge.
|
||||
streets.eachLayer(streetToGraphBound);
|
||||
let graph = createGraph(),
|
||||
streetToGraphBound = streetToGraph.bind(this, graph);
|
||||
|
||||
return graph;
|
||||
//For each street, get an array of indices for the start, intersections, and end coordinates, in order from
|
||||
//start to end. Then, add the coordinates at each index as a node, and an edge between each adjacent node in the array,
|
||||
//associating the distance between the nodes (between their coordinates) with each edge.
|
||||
streets.eachLayer(streetToGraphBound);
|
||||
|
||||
return graph;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -35,71 +35,81 @@ function streetsToGraph(streets) {
|
||||
* @param {L.Polyline} street - A Leaflet Polyline layer for a street.
|
||||
*/
|
||||
function streetToGraph(graph, street) {
|
||||
let street_id = street._leaflet_id,
|
||||
intersection_indices = [],
|
||||
street_points = street.getLatLngs();
|
||||
|
||||
//Populate intersection_indices with the indices of all of the street's intersections in its coordinate array.
|
||||
for (let cross_street in street.intersections) {
|
||||
let intersections = street.intersections[cross_street];
|
||||
|
||||
for (let intersection of intersections) {
|
||||
let intersection_index = intersection[1][street_id];
|
||||
|
||||
//Ignore duplicate intersection points (caused by 3-way intersections).
|
||||
if (!intersection_indices.some(other_intersection_index => other_intersection_index === intersection_index)) {
|
||||
intersection_indices.push(intersection_index);
|
||||
}
|
||||
}
|
||||
}
|
||||
let street_id = street._leaflet_id,
|
||||
intersection_indices = [],
|
||||
street_points = street.getLatLngs();
|
||||
|
||||
//Sort the intersection_indices so that they are in order from the start of the street's coordinate array to the end;
|
||||
//this is why we're not getting the raw coordinates, but their indices first, so they can be sorted.
|
||||
intersection_indices = intersection_indices.sort(function(a, b) {
|
||||
return a - b;
|
||||
});
|
||||
//Populate intersection_indices with the indices of all of the street's intersections in its coordinate array.
|
||||
for (let cross_street in street.intersections) {
|
||||
let intersections = street.intersections[cross_street];
|
||||
|
||||
//Check if beginning and end points of the street are in the intersection_incides; if not, add them.
|
||||
if (!intersection_indices.some(intersection_index => intersection_index === 0)) {
|
||||
intersection_indices.unshift(0);
|
||||
}
|
||||
if (!intersection_indices.some(intersection_index => intersection_index === street_points.length - 1)) {
|
||||
intersection_indices.push(street_points.length - 1);
|
||||
}
|
||||
for (let intersection of intersections) {
|
||||
let intersection_index = intersection[1][street_id];
|
||||
|
||||
//Make a graph out of segments of the street between the start, intersections, and end of the street,
|
||||
//so that the nodes are the coordinates of the start, end, and intersection points, and the edges are
|
||||
//the segments between successive nodes. Each edge is associated with the geographic distance between its nodes.
|
||||
for (let i = 0; i <= intersection_indices.length - 2; i++) {
|
||||
let node_a = street_points[intersection_indices[i]],
|
||||
node_b = street_points[intersection_indices[i + 1]],
|
||||
a_string = encodeLatLng(node_a),
|
||||
b_string = encodeLatLng(node_b),
|
||||
start_coords = L.A.pointToCoordinateArray(node_a),
|
||||
end_coords = L.A.pointToCoordinateArray(node_b),
|
||||
segment = lineSlice(start_coords, end_coords, street.toGeoJSON()),
|
||||
distance = length(segment);
|
||||
graph.addLink(a_string, b_string, {
|
||||
distance: distance,
|
||||
place: { type: "street",
|
||||
id: street_id }
|
||||
});
|
||||
}
|
||||
//Ignore duplicate intersection points (caused by 3-way intersections).
|
||||
if (
|
||||
!intersection_indices.some(
|
||||
(other_intersection_index) =>
|
||||
other_intersection_index === intersection_index,
|
||||
)
|
||||
) {
|
||||
intersection_indices.push(intersection_index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Sort the intersection_indices so that they are in order from the start of the street's coordinate array to the end;
|
||||
//this is why we're not getting the raw coordinates, but their indices first, so they can be sorted.
|
||||
intersection_indices = intersection_indices.sort(function (a, b) {
|
||||
return a - b;
|
||||
});
|
||||
|
||||
//Check if beginning and end points of the street are in the intersection_incides; if not, add them.
|
||||
if (
|
||||
!intersection_indices.some((intersection_index) => intersection_index === 0)
|
||||
) {
|
||||
intersection_indices.unshift(0);
|
||||
}
|
||||
if (
|
||||
!intersection_indices.some(
|
||||
(intersection_index) => intersection_index === street_points.length - 1,
|
||||
)
|
||||
) {
|
||||
intersection_indices.push(street_points.length - 1);
|
||||
}
|
||||
|
||||
//Make a graph out of segments of the street between the start, intersections, and end of the street,
|
||||
//so that the nodes are the coordinates of the start, end, and intersection points, and the edges are
|
||||
//the segments between successive nodes. Each edge is associated with the geographic distance between its nodes.
|
||||
for (let i = 0; i <= intersection_indices.length - 2; i++) {
|
||||
let node_a = street_points[intersection_indices[i]],
|
||||
node_b = street_points[intersection_indices[i + 1]],
|
||||
a_string = encodeLatLng(node_a),
|
||||
b_string = encodeLatLng(node_b),
|
||||
start_coords = L.A.pointToCoordinateArray(node_a),
|
||||
end_coords = L.A.pointToCoordinateArray(node_b),
|
||||
segment = lineSlice(start_coords, end_coords, street.toGeoJSON()),
|
||||
distance = length(segment);
|
||||
graph.addLink(a_string, b_string, {
|
||||
distance: distance,
|
||||
place: { type: "street", id: street_id },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a street network (graph), return a pathfinder that can operate on it.
|
||||
* Useful if you modify the street graph during the simulation.
|
||||
*
|
||||
*
|
||||
* @param {object} graph - An ngraph graph representing an OSM street network.
|
||||
* @returns {object} - An A* pathfinder for the graph.
|
||||
*/
|
||||
function getPathFinder(graph) {
|
||||
return path.aStar(graph, {
|
||||
distance(fromNode, toNode, link) {
|
||||
return link.data.distance;
|
||||
}
|
||||
});
|
||||
return path.aStar(graph, {
|
||||
distance(fromNode, toNode, link) {
|
||||
return link.data.distance;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -115,71 +125,92 @@ function getPathFinder(graph) {
|
||||
* @param {Boolean} [sparse=false] - Whether to exclude intersections between the first and last along a street-specific path (which are superfluous for extracting the necessary sub-street).
|
||||
* @return {Array<Array<number>>} - An array of points along the graph, leading from the start to the end.
|
||||
*/
|
||||
function getPath(start_int_lat_lng, goal_int_lat_lng, start_lat_lng, goal_lat_lng, sparse = false) {
|
||||
let start_coord = encodeLatLng(start_int_lat_lng),
|
||||
end_coord = encodeLatLng(goal_int_lat_lng),
|
||||
encoded_path = this.pathfinder.find(start_coord, end_coord),
|
||||
path = [];
|
||||
|
||||
if (encoded_path.length > 0 && decodeCoordString(encoded_path[0].id).distanceTo(start_int_lat_lng) >
|
||||
decodeCoordString(encoded_path[0].id).distanceTo(goal_int_lat_lng)) {
|
||||
encoded_path = encoded_path.reverse();
|
||||
}
|
||||
function getPath(
|
||||
start_int_lat_lng,
|
||||
goal_int_lat_lng,
|
||||
start_lat_lng,
|
||||
goal_lat_lng,
|
||||
sparse = false,
|
||||
) {
|
||||
let start_coord = encodeLatLng(start_int_lat_lng),
|
||||
end_coord = encodeLatLng(goal_int_lat_lng),
|
||||
encoded_path = this.pathfinder.find(start_coord, end_coord),
|
||||
path = [];
|
||||
|
||||
if (sparse === true && encoded_path.length >= 2) {
|
||||
let sparse_path = [],
|
||||
recent_street = null,
|
||||
current_street = null;
|
||||
|
||||
for (let i = 0; i <= encoded_path.length - 2; i++) {
|
||||
current_street = this.streets.graph.getLink(encoded_path[i].id, encoded_path[i + 1].id) ||
|
||||
this.streets.graph.getLink(encoded_path[i + 1].id, encoded_path[i].id);
|
||||
|
||||
if (recent_street === null || current_street.data.place.id !== recent_street.data.place.id) {
|
||||
let decoded_coords = decodeCoordString(encoded_path[i].id, current_street.data.place);
|
||||
sparse_path.push(decoded_coords);
|
||||
}
|
||||
|
||||
//If the last place on the path to the goal is labeled with a different street id than the goal,
|
||||
//add it to the sparse path.
|
||||
if (i === encoded_path.length - 2) {
|
||||
let decoded_coords = decodeCoordString(encoded_path[i + 1].id, current_street.data.place);
|
||||
sparse_path.push(decoded_coords);
|
||||
}
|
||||
}
|
||||
|
||||
path = sparse_path;
|
||||
}
|
||||
else {
|
||||
path = encoded_path.map(point => decodeCoordString(point.id, 0));
|
||||
}
|
||||
|
||||
path.unshift(start_lat_lng);
|
||||
path.push(goal_lat_lng);
|
||||
|
||||
//If the goal point lies before the first intersection of the goal street, then the 2nd to last point in the
|
||||
//path will have the previous street's id attached to it. If the goal lies on a different street, make
|
||||
//sure the 2nd to last point (the street path intersection point before the goal) has the same street id as the goal.
|
||||
if (path[path.length - 2].new_place.id !== goal_lat_lng.new_place.id) {
|
||||
path[path.length - 2].new_place = goal_lat_lng.new_place;
|
||||
}
|
||||
if (
|
||||
encoded_path.length > 0 &&
|
||||
decodeCoordString(encoded_path[0].id).distanceTo(start_int_lat_lng) >
|
||||
decodeCoordString(encoded_path[0].id).distanceTo(goal_int_lat_lng)
|
||||
) {
|
||||
encoded_path = encoded_path.reverse();
|
||||
}
|
||||
|
||||
//If the second [to last] point--namely the intersection closest to the start [goal]--is further from the third
|
||||
//[to last] point than the goal, and all three points are on the same street, remove the second [to last] point.
|
||||
if (path.length >= 3) {
|
||||
checkStartExcess.call(this, path);
|
||||
checkEndExcess.call(this, path);
|
||||
}
|
||||
|
||||
return path;
|
||||
if (sparse === true && encoded_path.length >= 2) {
|
||||
let sparse_path = [],
|
||||
recent_street = null,
|
||||
current_street = null;
|
||||
|
||||
for (let i = 0; i <= encoded_path.length - 2; i++) {
|
||||
current_street =
|
||||
this.streets.graph.getLink(
|
||||
encoded_path[i].id,
|
||||
encoded_path[i + 1].id,
|
||||
) ||
|
||||
this.streets.graph.getLink(encoded_path[i + 1].id, encoded_path[i].id);
|
||||
|
||||
if (
|
||||
recent_street === null ||
|
||||
current_street.data.place.id !== recent_street.data.place.id
|
||||
) {
|
||||
let decoded_coords = decodeCoordString(
|
||||
encoded_path[i].id,
|
||||
current_street.data.place,
|
||||
);
|
||||
sparse_path.push(decoded_coords);
|
||||
}
|
||||
|
||||
//If the last place on the path to the goal is labeled with a different street id than the goal,
|
||||
//add it to the sparse path.
|
||||
if (i === encoded_path.length - 2) {
|
||||
let decoded_coords = decodeCoordString(
|
||||
encoded_path[i + 1].id,
|
||||
current_street.data.place,
|
||||
);
|
||||
sparse_path.push(decoded_coords);
|
||||
}
|
||||
}
|
||||
|
||||
path = sparse_path;
|
||||
} else {
|
||||
path = encoded_path.map((point) => decodeCoordString(point.id, 0));
|
||||
}
|
||||
|
||||
path.unshift(start_lat_lng);
|
||||
path.push(goal_lat_lng);
|
||||
|
||||
//If the goal point lies before the first intersection of the goal street, then the 2nd to last point in the
|
||||
//path will have the previous street's id attached to it. If the goal lies on a different street, make
|
||||
//sure the 2nd to last point (the street path intersection point before the goal) has the same street id as the goal.
|
||||
if (path[path.length - 2].new_place.id !== goal_lat_lng.new_place.id) {
|
||||
path[path.length - 2].new_place = goal_lat_lng.new_place;
|
||||
}
|
||||
|
||||
//If the second [to last] point--namely the intersection closest to the start [goal]--is further from the third
|
||||
//[to last] point than the goal, and all three points are on the same street, remove the second [to last] point.
|
||||
if (path.length >= 3) {
|
||||
checkStartExcess.call(this, path);
|
||||
checkEndExcess.call(this, path);
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
//checkStartExcess and checkEndExcess are _much_ easier to follow given distinct variable names,
|
||||
//and so they are not abstracted into one more general function.
|
||||
|
||||
/**
|
||||
/**
|
||||
* If the first two points after the start point share the same street as the start point, and the
|
||||
* third point is closer to the first (start) point than it is to the second point, remove the
|
||||
* third point is closer to the first (start) point than it is to the second point, remove the
|
||||
* second point, as it's a superfluous detour.<br/><br/>
|
||||
*
|
||||
* Typically happens when the start point's nearest intersection is beyond it on the street,
|
||||
@ -190,30 +221,41 @@ function getPath(start_int_lat_lng, goal_int_lat_lng, start_lat_lng, goal_lat_ln
|
||||
* @param {Array<LatLng>} path - An array of LatLngs representing a path for an agent to travel along.
|
||||
*/
|
||||
function checkStartExcess(path) {
|
||||
let start_street = this.streets.getLayer(path[0].new_place.id),
|
||||
second_street_id = path[1].new_place.id,
|
||||
start_second_intersections = start_street.intersections[second_street_id],
|
||||
second_is_intersection = typeof(start_second_intersections) === "undefined" ? false :
|
||||
start_second_intersections.some(intersection =>
|
||||
intersection[0].lat === path[1].lat && intersection[0].lng === path[1].lng),
|
||||
third_street_id = path[2].new_place.id,
|
||||
start_third_intersections = start_street.intersections[third_street_id],
|
||||
third_is_intersection = typeof(start_third_intersections) === "undefined" ? false :
|
||||
start_third_intersections.some(intersection =>
|
||||
intersection[0].lat === path[2].lat && intersection[0].lng === path[2].lng);
|
||||
let start_street = this.streets.getLayer(path[0].new_place.id),
|
||||
second_street_id = path[1].new_place.id,
|
||||
start_second_intersections = start_street.intersections[second_street_id],
|
||||
second_is_intersection =
|
||||
typeof start_second_intersections === "undefined"
|
||||
? false
|
||||
: start_second_intersections.some(
|
||||
(intersection) =>
|
||||
intersection[0].lat === path[1].lat &&
|
||||
intersection[0].lng === path[1].lng,
|
||||
),
|
||||
third_street_id = path[2].new_place.id,
|
||||
start_third_intersections = start_street.intersections[third_street_id],
|
||||
third_is_intersection =
|
||||
typeof start_third_intersections === "undefined"
|
||||
? false
|
||||
: start_third_intersections.some(
|
||||
(intersection) =>
|
||||
intersection[0].lat === path[2].lat &&
|
||||
intersection[0].lng === path[2].lng,
|
||||
);
|
||||
|
||||
if ((second_is_intersection || second_street_id === path[0].new_place.id) &&
|
||||
(third_is_intersection || third_street_id === path[0].new_place.id)) {
|
||||
if (path[2].distanceTo(path[0]) <
|
||||
path[2].distanceTo(path[1])) {
|
||||
path.splice(1, 1);
|
||||
}
|
||||
}
|
||||
if (
|
||||
(second_is_intersection || second_street_id === path[0].new_place.id) &&
|
||||
(third_is_intersection || third_street_id === path[0].new_place.id)
|
||||
) {
|
||||
if (path[2].distanceTo(path[0]) < path[2].distanceTo(path[1])) {
|
||||
path.splice(1, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* If the last two points before the goal point share the same street as the goal point, and the
|
||||
* first point is closer to the third (goal) point than it is to the second point, remove the
|
||||
* first point is closer to the third (goal) point than it is to the second point, remove the
|
||||
* second point, as it's a superfluous detour.<br/><br/>
|
||||
*
|
||||
* Typically happens when the goal point's nearest intersection is beyond it on the street,
|
||||
@ -225,26 +267,44 @@ function checkStartExcess(path) {
|
||||
* @param {Array<LatLng>} path - An array of LatLngs representing a path for an agent to travel along.
|
||||
*/
|
||||
function checkEndExcess(path) {
|
||||
let goal_street = this.streets.getLayer(path[path.length - 1].new_place.id),
|
||||
second_to_last_street_id = path[path.length - 2].new_place.id,
|
||||
goal_second_to_last_intersections = goal_street.intersections[second_to_last_street_id],
|
||||
second_to_last_is_intersection = typeof(goal_second_to_last_intersections) === "undefined" ? false :
|
||||
goal_second_to_last_intersections.some(intersection =>
|
||||
intersection[0].lat === path[path.length - 1].lat && intersection[0].lng === path[path.length - 1].lng),
|
||||
third_last_street_id = path[path.length - 3].new_place.id,
|
||||
goal_third_last_intersections = goal_street.intersections[third_last_street_id],
|
||||
third_last_is_intersection = typeof(goal_third_last_intersections) === "undefined" ? false :
|
||||
goal_third_last_intersections.some(intersection =>
|
||||
intersection[0].lat === path[path.length - 3].lat && intersection[0].lng === path[path.length - 3].lng);
|
||||
let goal_street = this.streets.getLayer(path[path.length - 1].new_place.id),
|
||||
second_to_last_street_id = path[path.length - 2].new_place.id,
|
||||
goal_second_to_last_intersections =
|
||||
goal_street.intersections[second_to_last_street_id],
|
||||
second_to_last_is_intersection =
|
||||
typeof goal_second_to_last_intersections === "undefined"
|
||||
? false
|
||||
: goal_second_to_last_intersections.some(
|
||||
(intersection) =>
|
||||
intersection[0].lat === path[path.length - 1].lat &&
|
||||
intersection[0].lng === path[path.length - 1].lng,
|
||||
),
|
||||
third_last_street_id = path[path.length - 3].new_place.id,
|
||||
goal_third_last_intersections =
|
||||
goal_street.intersections[third_last_street_id],
|
||||
third_last_is_intersection =
|
||||
typeof goal_third_last_intersections === "undefined"
|
||||
? false
|
||||
: goal_third_last_intersections.some(
|
||||
(intersection) =>
|
||||
intersection[0].lat === path[path.length - 3].lat &&
|
||||
intersection[0].lng === path[path.length - 3].lng,
|
||||
);
|
||||
|
||||
if ((second_to_last_is_intersection || second_to_last_street_id === path[path.length - 1].new_place.id) &&
|
||||
(third_last_is_intersection || third_last_street_id === path[path.length - 1].new_place.id) &&
|
||||
path.length >= 3) {
|
||||
if (path[path.length - 3].distanceTo(path[path.length - 1]) <
|
||||
path[path.length - 3].distanceTo(path[path.length - 2])) {
|
||||
path.splice(path.length - 2, 1);
|
||||
}
|
||||
}
|
||||
if (
|
||||
(second_to_last_is_intersection ||
|
||||
second_to_last_street_id === path[path.length - 1].new_place.id) &&
|
||||
(third_last_is_intersection ||
|
||||
third_last_street_id === path[path.length - 1].new_place.id) &&
|
||||
path.length >= 3
|
||||
) {
|
||||
if (
|
||||
path[path.length - 3].distanceTo(path[path.length - 1]) <
|
||||
path[path.length - 3].distanceTo(path[path.length - 2])
|
||||
) {
|
||||
path.splice(path.length - 2, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -255,7 +315,7 @@ function checkEndExcess(path) {
|
||||
* @returns {string} - A string containing coordinates in the format of "Latitude,Longitude".
|
||||
*/
|
||||
function encodeLatLng(lat_lng) {
|
||||
return lat_lng.lat.toString() + "," + lat_lng.lng.toString();
|
||||
return lat_lng.lat.toString() + "," + lat_lng.lng.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -267,11 +327,11 @@ function encodeLatLng(lat_lng) {
|
||||
* @returns {LatLng} - The coordinates encoded by the coord_string.
|
||||
*/
|
||||
function decodeCoordString(coord_string, place) {
|
||||
let coord_strings = coord_string.split(","),
|
||||
lat_lng = L.latLng(coord_strings);
|
||||
lat_lng.new_place = place;
|
||||
let coord_strings = coord_string.split(","),
|
||||
lat_lng = L.latLng(coord_strings);
|
||||
lat_lng.new_place = place;
|
||||
|
||||
return lat_lng;
|
||||
return lat_lng;
|
||||
}
|
||||
|
||||
Agentmap.prototype.getPath = getPath;
|
||||
|
||||
@ -5,28 +5,28 @@
|
||||
/* Core of a command line tool that runs buildingify and exports the resulting street and unit layers. */
|
||||
|
||||
let optimist = require("optimist"),
|
||||
fs = require("fs"),
|
||||
path = require("path");
|
||||
fs = require("fs"),
|
||||
path = require("path");
|
||||
|
||||
//Mock a browser environment.
|
||||
var window = {
|
||||
screen: {}
|
||||
},
|
||||
document = {
|
||||
documentElement: {
|
||||
style: []
|
||||
},
|
||||
createElement: () => ({
|
||||
getContext: null
|
||||
}),
|
||||
},
|
||||
navigator = {
|
||||
userAgent: {
|
||||
toLowerCase: () => ""
|
||||
},
|
||||
platform: ""
|
||||
},
|
||||
requestAnimationFrame = () => null;
|
||||
screen: {},
|
||||
},
|
||||
document = {
|
||||
documentElement: {
|
||||
style: [],
|
||||
},
|
||||
createElement: () => ({
|
||||
getContext: null,
|
||||
}),
|
||||
},
|
||||
navigator = {
|
||||
userAgent: {
|
||||
toLowerCase: () => "",
|
||||
},
|
||||
platform: "",
|
||||
},
|
||||
requestAnimationFrame = () => null;
|
||||
|
||||
//Setup the Leaflet namespace.
|
||||
let L = require("leaflet");
|
||||
@ -34,17 +34,17 @@ L.A = require(path.join("..", "src", "utils"));
|
||||
|
||||
//Logging functions.
|
||||
function new_status(text = "") {
|
||||
process.stdout.write(text);
|
||||
process.stdout.write(text);
|
||||
}
|
||||
|
||||
function status_update(text = "") {
|
||||
process.stdout.clearLine();
|
||||
process.stdout.write(text);
|
||||
process.stdout.cursorTo(0);
|
||||
process.stdout.clearLine();
|
||||
process.stdout.write(text);
|
||||
process.stdout.cursorTo(0);
|
||||
}
|
||||
|
||||
function end_status() {
|
||||
process.stdout.write("\n");
|
||||
process.stdout.write("\n");
|
||||
}
|
||||
|
||||
//Let other modules access L and the logging functions.
|
||||
@ -55,8 +55,8 @@ exports.end_status = end_status;
|
||||
|
||||
//Mock an AgentMaps object.
|
||||
function AgentMock() {
|
||||
this.streets = null;
|
||||
this.units = null;
|
||||
this.streets = null;
|
||||
this.units = null;
|
||||
}
|
||||
|
||||
let browserlessbuildings = require("./browserlessbuildings");
|
||||
@ -67,47 +67,47 @@ let agentmock = new AgentMock();
|
||||
//Accept as input an array specifying the bounding box and
|
||||
//the name of a file containing the GeoJSON of streets wihin it.
|
||||
let bounding_box = JSON.parse(optimist.argv["bbox"]),
|
||||
OSM_file = path.normalize(optimist.argv["streets"]);
|
||||
OSM_file = path.normalize(optimist.argv["streets"]);
|
||||
|
||||
fs.readFile(OSM_file, "utf8", function(error, data) {
|
||||
readError(error);
|
||||
processFile(data)
|
||||
fs.readFile(OSM_file, "utf8", function (error, data) {
|
||||
readError(error);
|
||||
processFile(data);
|
||||
});
|
||||
|
||||
//Given the bounding box and OSM data, extract the necessary info,
|
||||
//generate the buildings (streets and units), and save them as files.
|
||||
function processFile(data) {
|
||||
let start = data.indexOf("{");
|
||||
data = data.slice(start);
|
||||
let start = data.indexOf("{");
|
||||
data = data.slice(start);
|
||||
|
||||
if (data[data.length - 1] !== "}") {
|
||||
data = data.slice(0, -1);
|
||||
}
|
||||
|
||||
let OSM_data = JSON.parse(data);
|
||||
|
||||
agentmock.buildingify(bounding_box, OSM_data);
|
||||
|
||||
let streets = agentmock.streets.toGeoJSON(20),
|
||||
units = agentmock.units.toGeoJSON(20);
|
||||
if (data[data.length - 1] !== "}") {
|
||||
data = data.slice(0, -1);
|
||||
}
|
||||
|
||||
let streets_contents = "var streets_data = " + JSON.stringify(streets) + ";",
|
||||
units_contents = "var units_data = " + JSON.stringify(units) + ";";
|
||||
|
||||
fs.writeFile("street_features.js", streets_contents, writeError);
|
||||
fs.writeFile("unit_features.js", units_contents, writeError);
|
||||
let OSM_data = JSON.parse(data);
|
||||
|
||||
agentmock.buildingify(bounding_box, OSM_data);
|
||||
|
||||
let streets = agentmock.streets.toGeoJSON(20),
|
||||
units = agentmock.units.toGeoJSON(20);
|
||||
|
||||
let streets_contents = "var streets_data = " + JSON.stringify(streets) + ";",
|
||||
units_contents = "var units_data = " + JSON.stringify(units) + ";";
|
||||
|
||||
fs.writeFile("street_features.js", streets_contents, writeError);
|
||||
fs.writeFile("unit_features.js", units_contents, writeError);
|
||||
}
|
||||
|
||||
function writeError(error) {
|
||||
if (error) {
|
||||
let prefix = "There was an issue saving your data: ";
|
||||
return console.log(prefix + error);
|
||||
}
|
||||
if (error) {
|
||||
let prefix = "There was an issue saving your data: ";
|
||||
return console.log(prefix + error);
|
||||
}
|
||||
}
|
||||
|
||||
function readError(error) {
|
||||
if (error) {
|
||||
let prefix = "There was an issue accessing your data: ";
|
||||
return console.log(prefix + error);
|
||||
}
|
||||
if (error) {
|
||||
let prefix = "There was an issue accessing your data: ";
|
||||
return console.log(prefix + error);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user