The Roadmap Generator is a tool I wrote with the aim of procedurally generating road maps of large cities.
There are two versions:
The new version of Roadmap Generation is thought to be a Google-Maps-looking maps generator to create custom and completely made-up geographic road maps. Starting from a blank background, the user can draw 5 typologies of elements:
The main component of the software architecture handles a sort of state machine to define which type of operation is on the way to be performed by the user. For example, in the case of the roads, there can be the following states:
The user can draw the roads by clicking on the screen to add the points that compose the road path. In the initial creation mode the user clicks point by point to draw the path; when the creation is terminated the road element's data is added to the scene "database" and then a name for the road can be chosen. After that, the user can edit the line points at any time, to fix and adjust the path and add new points to extend the road. By clicking on the road from the map or selecting it from the roads outline, the editing mode is activated. Here the user can move the points by simply dragging them or adding new ones by ctrl+clicking along the road line.
About the software architecture, every element has two logic services: the service responsible for the allocation, editing and creation of the element (which handles coordinates, types definition, selection) and the service responsible for the appearance of the element, namely the actual rendering style, based on its position, its type and eventual other properties.
So, in the case of the road I wrote two services: the RoadService
(extending the AbstractElementService<T>
) and the RoadGraphicService
.
When the canvas click event occurres in editing mode, the point coordinates are emitted, and then the service add them to the list the made up the road line. In the creation or editing stage the Graphic Service renders differently, clearly showing the control points. The main component extracts the click coordinate with the following method:
private getCursorPosition(canvas, event): Point {
const rect = canvas.getBoundingClientRect(), // abs. size of element
scaleX = canvas.width / rect.width, // relationship bitmap vs. element for X
scaleY = canvas.height / rect.height; // relationship bitmap vs. element for Y
let point: Point = {
x: Math.round((event.clientX - rect.left) * scaleX), // scale mouse coordinates after they have
y: Math.round((event.clientY - rect.top) * scaleY) // been adjusted to be relative to element
};
point.x = point.x / this.gs.zoom - (((this.panX * this.zoom) + this.gs.zoomShiftX) / this.gs.zoom);
point.y = point.y / this.gs.zoom - (((this.panY * this.zoom) + this.gs.zoomShiftY) / this.gs.zoom);
return point;
}
The RoadService
, also, listens for the click on canvas to edit the road line and handles the properties of the road based on the type.
The GeometryService
exposes several trigonometric or geometric functions to perform some recurring operation, such as verify that a point is on a segment defined by two other points. In method below the RoadService
tries to understand if the user is clicking to edit some road or less, so makes use of the isBetween()
method of the GeometryService
to verify if the clicked point lies along one of the segments of one of the road lines of the scene. Of course, this is performed considering a minimum possible deviation, because the human click can easily not be precise at the pixel. This threshold is defined from the service's constant ROAD_SNAP_TOLERANCE
.
trySelect(point: Point): boolean {
for (let j = 0; j < this.ds.LINES.length; j++) {
var road = this.ds.LINES[j];
for (let i = 0; i < road.lines.length - 1; i++) {
const result = this.geometryService.isBetween(road.lines[i], road.lines[i + 1], point, this.ROAD_SNAP_TOLERANCE);
if (result === true) {
this.edit(j);
return true;
}
}
}
return false;
}
If this interception occurs on the editing road state, and the CTRL key is down, then a new point is added between the lines. In the same way the service tries to understand if the user is dragging an existing point by comparing the mouse coordinates with each single point of the road line.
The special feature of the highway junctions and of the roundabouts is that they are able to automatically adapt their shape to follow the angle of interception of the two roads they are joining. For example a roundabout placed on a cross intersection of 90° will be perfectly perpendicular, but a little shear will be applied to the roundabout placed on an intersection of different angle. Placing junctions and rounds the user will experiment with a sort of "snapping" of the element along the two roads.