Improve label rendering on maps
This commit is contained in:
BIN
map-generator/circle.png
Normal file
BIN
map-generator/circle.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 354 B |
4
map-generator/circle.svg
Normal file
4
map-generator/circle.svg
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg version="1.1" id="circle" xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 15 15">
|
||||||
|
<path d="M14,7.5c0,3.5899-2.9101,6.5-6.5,6.5S1,11.0899,1,7.5S3.9101,1,7.5,1S14,3.9101,14,7.5z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 254 B |
2
map-generator/dist/main.js
vendored
2
map-generator/dist/main.js
vendored
File diff suppressed because one or more lines are too long
@@ -21,166 +21,176 @@ mapboxgl.accessToken = 'pk.eyJ1IjoibWF0dHl3YXkiLCJhIjoiY2x6eG9vMzZyMHY2cDJqb3M1O
|
|||||||
const map = new mapboxgl.Map({
|
const map = new mapboxgl.Map({
|
||||||
container: 'map',
|
container: 'map',
|
||||||
zoom: 10,
|
zoom: 10,
|
||||||
style: 'mapbox://styles/mattyway/clzy2ozzf004k01pn840h9xdb',
|
style: 'mapbox://styles/mattyway/cm03vw57q00fd01pn93wf2j7p',
|
||||||
center: [145.00724,-37.79011]
|
center: [145.00724,-37.79011]
|
||||||
});
|
});
|
||||||
|
|
||||||
fetch("wards_withboundaries.json")
|
map.on('load', () => {
|
||||||
.then(response => {
|
// Load an image from an external URL.
|
||||||
response.json()
|
map.loadImage('circle.png', (error, image) => {
|
||||||
.then((wardData) => {
|
if (error) throw error;
|
||||||
const filteredWardData = wardData.filter((ward) => normaliseCouncilName(ward.parentElectorateName) == councilName);
|
|
||||||
|
|
||||||
var bounds = {
|
map.addImage('blue-circle', image);
|
||||||
"west": undefined,
|
|
||||||
"south": undefined,
|
|
||||||
"east": undefined,
|
|
||||||
"north": undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
function addToBounds(coordinate) {
|
fetch("wards_withboundaries.json")
|
||||||
if (bounds.west == undefined || coordinate[0] < bounds.west) {
|
.then(response => {
|
||||||
bounds.west = coordinate[0];
|
response.json()
|
||||||
}
|
.then((wardData) => {
|
||||||
|
const filteredWardData = wardData.filter((ward) => normaliseCouncilName(ward.parentElectorateName) == councilName);
|
||||||
|
|
||||||
if (bounds.south == undefined || coordinate[1] < bounds.south) {
|
var bounds = {
|
||||||
bounds.south = coordinate[1];
|
"west": undefined,
|
||||||
}
|
"south": undefined,
|
||||||
|
"east": undefined,
|
||||||
|
"north": undefined
|
||||||
|
}
|
||||||
|
|
||||||
if (bounds.east == undefined || coordinate[0] > bounds.east) {
|
function addToBounds(coordinate) {
|
||||||
bounds.east = coordinate[0];
|
if (bounds.west == undefined || coordinate[0] < bounds.west) {
|
||||||
}
|
bounds.west = coordinate[0];
|
||||||
|
|
||||||
if (bounds.north == undefined || coordinate[1] > bounds.north) {
|
|
||||||
bounds.north = coordinate[1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var labelFeatures = [];
|
|
||||||
|
|
||||||
filteredWardData.forEach(wardData => {
|
|
||||||
const featureCollection = {
|
|
||||||
'type': 'FeatureCollection',
|
|
||||||
'features': [
|
|
||||||
{
|
|
||||||
'type': 'Feature',
|
|
||||||
'geometry': JSON.parse(wardData.boundaryJson)
|
|
||||||
}
|
}
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
if (featureCollection.features[0].geometry.type == "Polygon") {
|
if (bounds.south == undefined || coordinate[1] < bounds.south) {
|
||||||
featureCollection.features[0].geometry.coordinates[0].forEach(coordinate => {
|
bounds.south = coordinate[1];
|
||||||
addToBounds(coordinate);
|
}
|
||||||
});
|
|
||||||
}
|
if (bounds.east == undefined || coordinate[0] > bounds.east) {
|
||||||
if (featureCollection.features[0].geometry.type == "MultiPolygon") {
|
bounds.east = coordinate[0];
|
||||||
featureCollection.features[0].geometry.coordinates.forEach(polygon => {
|
}
|
||||||
polygon[0].forEach(coordinate => {
|
|
||||||
addToBounds(coordinate);
|
if (bounds.north == undefined || coordinate[1] > bounds.north) {
|
||||||
|
bounds.north = coordinate[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var labelFeatures = [];
|
||||||
|
|
||||||
|
filteredWardData.forEach(wardData => {
|
||||||
|
const featureCollection = {
|
||||||
|
'type': 'FeatureCollection',
|
||||||
|
'features': [
|
||||||
|
{
|
||||||
|
'type': 'Feature',
|
||||||
|
'geometry': JSON.parse(wardData.boundaryJson)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
if (featureCollection.features[0].geometry.type == "Polygon") {
|
||||||
|
featureCollection.features[0].geometry.coordinates[0].forEach(coordinate => {
|
||||||
|
addToBounds(coordinate);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (featureCollection.features[0].geometry.type == "MultiPolygon") {
|
||||||
|
featureCollection.features[0].geometry.coordinates.forEach(polygon => {
|
||||||
|
polygon[0].forEach(coordinate => {
|
||||||
|
addToBounds(coordinate);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add data
|
||||||
|
map.addSource("data_"+wardData.electorateId, {
|
||||||
|
'type': 'geojson',
|
||||||
|
'data': featureCollection
|
||||||
});
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add data
|
// Add a line along the data
|
||||||
map.addSource("data_"+wardData.electorateId, {
|
map.addLayer({
|
||||||
'type': 'geojson',
|
'id': "outline_"+wardData.electorateId,
|
||||||
'data': featureCollection
|
'type': 'line',
|
||||||
});
|
'source': "data_"+wardData.electorateId,
|
||||||
|
'layout': {},
|
||||||
|
'paint': {
|
||||||
|
'line-color': '#0899fe',
|
||||||
|
'line-width': 3
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Add a line along the data
|
var centrePoint;
|
||||||
map.addLayer({
|
if (featureCollection.features[0].geometry.type == "Polygon") {
|
||||||
'id': "outline_"+wardData.electorateId,
|
centrePoint = polylabel(featureCollection.features[0].geometry.coordinates, 0.000001);
|
||||||
'type': 'line',
|
}
|
||||||
'source': "data_"+wardData.electorateId,
|
if (featureCollection.features[0].geometry.type == "MultiPolygon") {
|
||||||
'layout': {},
|
// TODO: Find the biggest polygon in the multipolygon and use that to find the centre point
|
||||||
'paint': {
|
// instead of just picking the second polygon.
|
||||||
'line-color': '#0899fe',
|
//
|
||||||
'line-width': 3
|
// The 2024 set of boundaries only uses 2 MultiPolygon objects (Cathedral in Murrindindi Shire Council and Island in Bass Coast Shire Council)
|
||||||
}
|
// Luckily, the second polygon for both objects results in a good label placement.
|
||||||
});
|
centrePoint = polylabel(featureCollection.features[0].geometry.coordinates[1], 0.000001);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
var centrePoint;
|
if (wardData.electorateName.includes(' ')) {
|
||||||
if (featureCollection.features[0].geometry.type == "Polygon") {
|
// Breaking long names into newlines looks better
|
||||||
centrePoint = polylabel(featureCollection.features[0].geometry.coordinates, 0.000001);
|
const parts = wardData.electorateName.split(' ');
|
||||||
}
|
// Special case if a ward starts with "St" (like "St Albans East")
|
||||||
if (featureCollection.features[0].geometry.type == "MultiPolygon") {
|
// Join the first two parts
|
||||||
// TODO: Find the biggest polygon in the multipolygon and use that to find the centre point
|
if (parts[0] == "St") {
|
||||||
// instead of just picking the second polygon.
|
parts[0] = parts[0] + ' ' + parts[1];
|
||||||
//
|
parts.splice(1, 1);
|
||||||
// The 2024 set of boundaries only uses 2 MultiPolygon objects (Cathedral in Murrindindi Shire Council and Island in Bass Coast Shire Council)
|
}
|
||||||
// Luckily, the second polygon for both objects results in a good label placement.
|
const wardNameNewLines = parts.join('\n');
|
||||||
centrePoint = polylabel(featureCollection.features[0].geometry.coordinates[1], 0.000001);
|
labelFeatures.push({
|
||||||
}
|
'type': 'Feature',
|
||||||
|
'properties': {
|
||||||
|
'description': wardNameNewLines
|
||||||
if (wardData.electorateName.includes(' ')) {
|
},
|
||||||
// Breaking long names into newlines looks better
|
'geometry': {
|
||||||
const parts = wardData.electorateName.split(' ');
|
'type': 'Point',
|
||||||
// Special case if a ward starts with "St" (like "St Albans East")
|
'coordinates': centrePoint
|
||||||
// Join the first two parts
|
}
|
||||||
if (parts[0] == "St") {
|
});
|
||||||
parts[0] = parts[0] + ' ' + parts[1];
|
} else {
|
||||||
parts.splice(1, 1);
|
labelFeatures.push({
|
||||||
}
|
'type': 'Feature',
|
||||||
const wardNameNewLines = parts.join('\n');
|
'properties': {
|
||||||
labelFeatures.push({
|
'description': wardData.electorateName
|
||||||
'type': 'Feature',
|
},
|
||||||
'properties': {
|
'geometry': {
|
||||||
'description': wardNameNewLines
|
'type': 'Point',
|
||||||
},
|
'coordinates': centrePoint
|
||||||
'geometry': {
|
}
|
||||||
'type': 'Point',
|
});
|
||||||
'coordinates': centrePoint
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
labelFeatures.push({
|
map.addSource('labels', {
|
||||||
'type': 'Feature',
|
'type': 'geojson',
|
||||||
'properties': {
|
'data': {
|
||||||
'description': wardData.electorateName
|
'type': 'FeatureCollection',
|
||||||
},
|
'features': labelFeatures
|
||||||
'geometry': {
|
|
||||||
'type': 'Point',
|
|
||||||
'coordinates': centrePoint
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
map.addSource('labels', {
|
map.addLayer({
|
||||||
'type': 'geojson',
|
'id': 'labels',
|
||||||
'data': {
|
'type': 'symbol',
|
||||||
'type': 'FeatureCollection',
|
'source': 'labels',
|
||||||
'features': labelFeatures
|
'layout': {
|
||||||
}
|
'text-field': ['get', 'description'],
|
||||||
});
|
'text-variable-anchor': ['top', 'left', 'bottom', 'right', 'top-left', 'top-right', 'bottom-left', 'bottom-right'],
|
||||||
|
'text-radial-offset': 1,
|
||||||
|
'text-padding': 0,
|
||||||
|
'text-justify': 'auto',
|
||||||
|
'text-allow-overlap': false,
|
||||||
|
'text-ignore-placement': false,
|
||||||
|
'icon-image': 'blue-circle'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
map.addLayer({
|
map.fitBounds([
|
||||||
'id': 'labels',
|
[bounds.west, bounds.south],
|
||||||
'type': 'symbol',
|
[bounds.east, bounds.north]
|
||||||
'source': 'labels',
|
], {
|
||||||
'layout': {
|
padding: 25,
|
||||||
'text-field': ['get', 'description'],
|
animate: false
|
||||||
'text-variable-anchor': ['center', 'top', 'bottom'],
|
});
|
||||||
'text-radial-offset': 0.5,
|
|
||||||
'text-padding': 0,
|
|
||||||
'text-justify': 'auto',
|
|
||||||
'text-allow-overlap': false,
|
|
||||||
'text-ignore-placement': false,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
map.fitBounds([
|
}).catch(err => {
|
||||||
[bounds.west, bounds.south],
|
console.log(err);
|
||||||
[bounds.east, bounds.north]
|
});
|
||||||
], {
|
|
||||||
padding: 25,
|
|
||||||
animate: false
|
|
||||||
});
|
|
||||||
|
|
||||||
}).catch(err => {
|
|
||||||
console.log(err);
|
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user