Compare commits
5 Commits
3718378545
...
8b3ddec283
| Author | SHA1 | Date | |
|---|---|---|---|
| 8b3ddec283 | |||
| 7950bab0c9 | |||
| 1d012f5ff1 | |||
| 95b7228778 | |||
| 7436c808e3 |
52
map-generator/capture-all-maps.js
Normal file
52
map-generator/capture-all-maps.js
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const { exec } = require('child_process');
|
||||||
|
|
||||||
|
var widthArgument = process.argv.at(2);
|
||||||
|
if (!widthArgument) {
|
||||||
|
console.log("Defaulting to width of 1080")
|
||||||
|
width = 1080;
|
||||||
|
} else {
|
||||||
|
width = parseInt(widthArgument);
|
||||||
|
if (isNaN(width)) {
|
||||||
|
console.log("Invalid width provided");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var heightArgument = process.argv.at(3);
|
||||||
|
if (!heightArgument) {
|
||||||
|
console.log("Defaulting to height of 720")
|
||||||
|
height = 720;
|
||||||
|
} else {
|
||||||
|
height = parseInt(heightArgument);
|
||||||
|
if (isNaN(height)) {
|
||||||
|
console.log("Invalid height provided");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var dataPathArgument = process.argv.at(4);
|
||||||
|
if (!dataPathArgument) {
|
||||||
|
console.log("Invalid data path provided");
|
||||||
|
exit(1);
|
||||||
|
} else {
|
||||||
|
dataPath = dataPathArgument;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sleep(ms) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
setTimeout(resolve, ms);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
var councils = JSON.parse(fs.readFileSync('../council_names.json', 'utf8'));
|
||||||
|
|
||||||
|
for (const council of councils) {
|
||||||
|
console.log("Generating map for " + council.slug + "...");
|
||||||
|
exec("node ./capture-map.js " + council.slug + " " + width + " " + height + " " + dataPath + "/" + council.slug + "/map.jpg");
|
||||||
|
|
||||||
|
// Need to slow down requests to avoid overloading the system...
|
||||||
|
await sleep(1000);
|
||||||
|
}
|
||||||
|
})();
|
||||||
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
12
map-generator/package-lock.json
generated
12
map-generator/package-lock.json
generated
@@ -5,6 +5,8 @@
|
|||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"child_process": "^1.0.2",
|
||||||
|
"fs": "^0.0.1-security",
|
||||||
"polylabel": "^2.0.1",
|
"polylabel": "^2.0.1",
|
||||||
"puppeteer-core": "^23.1.0"
|
"puppeteer-core": "^23.1.0"
|
||||||
}
|
}
|
||||||
@@ -201,6 +203,11 @@
|
|||||||
"node": "*"
|
"node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/child_process": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/child_process/-/child_process-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-Wmza/JzL0SiWz7kl6MhIKT5ceIlnFPJX+lwUGj7Clhy5MMldsSoJR0+uvRzOS5Kv45Mq7t1PoE8TsOA9bzvb6g=="
|
||||||
|
},
|
||||||
"node_modules/chromium-bidi": {
|
"node_modules/chromium-bidi": {
|
||||||
"version": "0.6.4",
|
"version": "0.6.4",
|
||||||
"resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.6.4.tgz",
|
"resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.6.4.tgz",
|
||||||
@@ -386,6 +393,11 @@
|
|||||||
"pend": "~1.2.0"
|
"pend": "~1.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/fs": {
|
||||||
|
"version": "0.0.1-security",
|
||||||
|
"resolved": "https://registry.npmjs.org/fs/-/fs-0.0.1-security.tgz",
|
||||||
|
"integrity": "sha512-3XY9e1pP0CVEUCdj5BmfIZxRBTSDycnbqhIOGec9QYtmVH2fbLpj86CFWkrNOkt/Fvty4KZG5lTglL9j/gJ87w=="
|
||||||
|
},
|
||||||
"node_modules/fs-extra": {
|
"node_modules/fs-extra": {
|
||||||
"version": "11.2.0",
|
"version": "11.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz",
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
{
|
{
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"child_process": "^1.0.2",
|
||||||
|
"fs": "^0.0.1-security",
|
||||||
"polylabel": "^2.0.1",
|
"polylabel": "^2.0.1",
|
||||||
"puppeteer-core": "^23.1.0"
|
"puppeteer-core": "^23.1.0"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,3 +13,5 @@ To automatically compile the `src.js` file after every edit, run `webpack-cli --
|
|||||||
Changes to `dist/main.js` should be committed so that other users don't need to install node.
|
Changes to `dist/main.js` should be committed so that other users don't need to install node.
|
||||||
|
|
||||||
The `capture-map.js` script can be used to capture an image of the map. It accepts 4 arguments, the council name, width, height, and output path. For example `node .\capture-map.js brimbank 900 500 ../../spl-data/brimbank/map.jpg` would capture a map of Brimbank City Council and place a file named `map.jpg` in the `../../spl-data/brimbank/` folder.
|
The `capture-map.js` script can be used to capture an image of the map. It accepts 4 arguments, the council name, width, height, and output path. For example `node .\capture-map.js brimbank 900 500 ../../spl-data/brimbank/map.jpg` would capture a map of Brimbank City Council and place a file named `map.jpg` in the `../../spl-data/brimbank/` folder.
|
||||||
|
|
||||||
|
The `capture-all-maps.js` script can be used to capture an image for each council. It accepts 3 arguments, width, height, and the path to the `spl-data` repo. For example `node .\capture-all-maps.js 900 500 ../../spl-data` would capture a map of each council and put it in the spl-data repo in the appropriate folder.
|
||||||
@@ -38,6 +38,24 @@ fetch("wards_withboundaries.json")
|
|||||||
"north": undefined
|
"north": undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addToBounds(coordinate) {
|
||||||
|
if (bounds.west == undefined || coordinate[0] < bounds.west) {
|
||||||
|
bounds.west = coordinate[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bounds.south == undefined || coordinate[1] < bounds.south) {
|
||||||
|
bounds.south = coordinate[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bounds.east == undefined || coordinate[0] > bounds.east) {
|
||||||
|
bounds.east = coordinate[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bounds.north == undefined || coordinate[1] > bounds.north) {
|
||||||
|
bounds.north = coordinate[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var labelFeatures = [];
|
var labelFeatures = [];
|
||||||
|
|
||||||
filteredWardData.forEach(wardData => {
|
filteredWardData.forEach(wardData => {
|
||||||
@@ -51,23 +69,18 @@ fetch("wards_withboundaries.json")
|
|||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
featureCollection.features[0].geometry.coordinates[0].forEach(coordinate => {
|
if (featureCollection.features[0].geometry.type == "Polygon") {
|
||||||
if (bounds.west == undefined || coordinate[0] < bounds.west) {
|
featureCollection.features[0].geometry.coordinates[0].forEach(coordinate => {
|
||||||
bounds.west = coordinate[0];
|
addToBounds(coordinate);
|
||||||
}
|
});
|
||||||
|
}
|
||||||
if (bounds.south == undefined || coordinate[1] < bounds.south) {
|
if (featureCollection.features[0].geometry.type == "MultiPolygon") {
|
||||||
bounds.south = coordinate[1];
|
featureCollection.features[0].geometry.coordinates.forEach(polygon => {
|
||||||
}
|
polygon[0].forEach(coordinate => {
|
||||||
|
addToBounds(coordinate);
|
||||||
if (bounds.east == undefined || coordinate[0] > bounds.east) {
|
});
|
||||||
bounds.east = coordinate[0];
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bounds.north == undefined || coordinate[1] > bounds.north) {
|
|
||||||
bounds.north = coordinate[1];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add data
|
// Add data
|
||||||
map.addSource("data_"+wardData.electorateId, {
|
map.addSource("data_"+wardData.electorateId, {
|
||||||
@@ -87,7 +100,19 @@ fetch("wards_withboundaries.json")
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const centrePoint = polylabel(featureCollection.features[0].geometry.coordinates, 0.000001);
|
var centrePoint;
|
||||||
|
if (featureCollection.features[0].geometry.type == "Polygon") {
|
||||||
|
centrePoint = polylabel(featureCollection.features[0].geometry.coordinates, 0.000001);
|
||||||
|
}
|
||||||
|
if (featureCollection.features[0].geometry.type == "MultiPolygon") {
|
||||||
|
// TODO: Find the biggest polygon in the multipolygon and use that to find the centre point
|
||||||
|
// instead of just picking the second polygon.
|
||||||
|
//
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if (wardData.electorateName.includes(' ')) {
|
if (wardData.electorateName.includes(' ')) {
|
||||||
// Breaking long names into newlines looks better
|
// Breaking long names into newlines looks better
|
||||||
|
|||||||
@@ -27,23 +27,23 @@ if (isset($options['candidates-file'])) {
|
|||||||
|
|
||||||
// Convert CSV into an array of dictionaries. Use the header as the key in the dictionary.
|
// Convert CSV into an array of dictionaries. Use the header as the key in the dictionary.
|
||||||
$candidateData = [];
|
$candidateData = [];
|
||||||
if (($handle = fopen($candidatesFile, "r")) !== FALSE) {
|
if (file_exists($candidatesFile)) {
|
||||||
$headers = fgetcsv($handle);
|
if (($handle = fopen($candidatesFile, "r")) !== FALSE) {
|
||||||
while (($data = fgetcsv($handle)) !== FALSE) {
|
$headers = fgetcsv($handle);
|
||||||
$candidate = [];
|
while (($data = fgetcsv($handle)) !== FALSE) {
|
||||||
foreach ($headers as $key => $value) {
|
$candidate = [];
|
||||||
$candidate[$value] = $data[$key];
|
foreach ($headers as $key => $value) {
|
||||||
|
$candidate[$value] = $data[$key];
|
||||||
|
}
|
||||||
|
$candidateData[] = $candidate;
|
||||||
}
|
}
|
||||||
$candidateData[] = $candidate;
|
fclose($handle);
|
||||||
|
} else {
|
||||||
|
error_log('Error opening candidates file');
|
||||||
|
exit(1);
|
||||||
}
|
}
|
||||||
fclose($handle);
|
|
||||||
} else {
|
} else {
|
||||||
error_log('Error opening candidates file');
|
error_log("The specified candidates.csv file does not exist, will not show any candidates for " . $councilData["shortName"] . ".");
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (empty($candidateData)) {
|
|
||||||
error_log("Failed to load any candidates for " . $councilData['shortName']);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isset($options['media-file'])) {
|
if (isset($options['media-file'])) {
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<!-- wp:paragraph {"align":"center"} -->
|
<!-- wp:paragraph -->
|
||||||
<p class="has-text-align-center">The Streets People Love campaign has created scorecards for candidates in the 2024 council elections. Scorecards have been generated based on a candidate's engagement with the Streets People Love campaign, their commitment to our pledge, their responses to a survey and input from campaign members located in the local government area in which they are running.</p>
|
<p>The Streets People Love campaign has created scorecards for candidates in the 2024 council elections. Scorecards have been generated based on a candidate's engagement with the Streets People Love campaign, their commitment to our pledge, their responses to a survey and input from campaign members located in the local government area in which they are running.</p>
|
||||||
<!-- /wp:paragraph -->
|
<!-- /wp:paragraph -->
|
||||||
|
|
||||||
<!-- wp:paragraph {"align":"center"} -->
|
<!-- wp:paragraph -->
|
||||||
<p class="has-text-align-center">Have candidates in your local government area not yet taken part? Send your local candidates the <a href="https://forms.gle/gnDNyBiVC64tDo2Y7">Streets People Love Pledge and Survey</a> and ask them to complete it so that local residents can vote for the candidates who want to build the streets people love.</p>
|
<p>Have candidates in your local government area not yet taken part? Send your local candidates the <a href="https://forms.gle/gnDNyBiVC64tDo2Y7">Streets People Love Pledge and Survey</a> and ask them to complete it so that local residents can vote for the candidates who want to build the streets people love.</p>
|
||||||
<!-- /wp:paragraph -->
|
<!-- /wp:paragraph -->
|
||||||
|
|
||||||
<?php if (isset($media["header.jpg"])): ?>
|
<?php if (isset($media["header.jpg"])): ?>
|
||||||
@@ -13,55 +13,80 @@
|
|||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
|
|
||||||
<?php foreach ($config['wardNames'] as $index => $wardName): ?>
|
<?php foreach ($config['wardNames'] as $index => $wardName): ?>
|
||||||
<!-- wp:heading {"level":3,"className":"is-style-default"} -->
|
<!-- wp:heading {"level":3,"className":"is-style-default"} -->
|
||||||
<?php $wardSlug = strtolower(str_replace(' ', '-', $wardName)); ?>
|
<?php $wardSlug = strtolower(str_replace(' ', '-', $wardName)); ?>
|
||||||
<h3 class="wp-block-heading is-style-default" id="<?php echo $wardSlug; ?>"><a style="text-decoration: none;" href="#<?php echo $wardSlug; ?>"><?php echo $wardName; ?></a></h3>
|
<h3 class="wp-block-heading is-style-default" id="<?php echo $wardSlug; ?>"><a style="text-decoration: none;" href="#<?php echo $wardSlug; ?>"><?php echo $wardName; ?></a></h3>
|
||||||
<!-- /wp:heading -->
|
<!-- /wp:heading -->
|
||||||
|
|
||||||
<?php
|
<?php
|
||||||
$wardCandidates = array_filter($candidates, function ($candidate) use ($wardName) {
|
$wardCandidates = array_filter($candidates, function ($candidate) use ($wardName) {
|
||||||
return isset($candidate["Ward"]) && $candidate["Ward"] === $wardName;
|
return isset($candidate["Ward"]) && $candidate["Ward"] === $wardName;
|
||||||
});
|
});
|
||||||
|
|
||||||
usort($wardCandidates, function($a, $b) {
|
usort($wardCandidates, function($a, $b) {
|
||||||
if ($a == $b) {
|
if ($a == $b) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return (((int) $a) < ((int) $b)) ? 1 : -1;
|
return (((int) $a) < ((int) $b)) ? 1 : -1;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (count($wardCandidates) > 0):
|
||||||
|
?>
|
||||||
|
|
||||||
if (count($wardCandidates) == 0) continue;
|
|
||||||
?>
|
|
||||||
<!-- wp:group {"style":{"spacing":{"padding":{"top":"0","bottom":"7rem"}}},"layout":{"type":"grid","columnCount":4}} -->
|
|
||||||
<div class="wp-block-group" style="padding-top:0;padding-bottom:7rem">
|
|
||||||
<?php foreach ($wardCandidates as $index => $candidate): ?>
|
|
||||||
<!-- wp:group {"layout":{"type":"flex","orientation":"vertical","justifyContent":"center"}} -->
|
|
||||||
<div class="wp-block-group">
|
|
||||||
<?php
|
<?php
|
||||||
if (isset($candidate['Picture']) && isset($media[$candidate['Picture']])) {
|
$columnCount = 4;
|
||||||
$candidate_image = $media[$candidate['Picture']];
|
|
||||||
} else {
|
$chunkedWardCandidates = array_chunk($wardCandidates, $columnCount);
|
||||||
$candidate_image = $media['default.png'];
|
|
||||||
}
|
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<!-- wp:image {"id":<?php echo $candidate_image['id']; ?>,"width":"200px","height":"200px","scale":"cover","style":{"color":{}},"className":"is-resized"} -->
|
<?php foreach($chunkedWardCandidates as $chunk): ?>
|
||||||
<figure class="wp-block-image is-resized"><img src="<?php echo $candidate_image['url']; ?>" alt="" class="wp-image-<?php echo $candidate_image['id']; ?>" style="object-fit:cover;width:200px;height:200px"/></figure>
|
<!-- wp:columns -->
|
||||||
<!-- /wp:image -->
|
<div class="wp-block-columns">
|
||||||
|
|
||||||
<!-- wp:heading {"fontSize":"medium"} -->
|
<?php for ($columnIdx = 0; $columnIdx < $columnCount; $columnIdx++): ?>
|
||||||
<h2 class="wp-block-heading has-medium-font-size"><strong><?php echo $candidate['Candidate Name']; ?></strong></h2>
|
<!-- wp:column -->
|
||||||
<!-- /wp:heading -->
|
<div class="wp-block-column">
|
||||||
|
|
||||||
<!-- wp:paragraph {"style":{"layout":{"selfStretch":"fit","flexSize":null}},"fontSize":"large"} -->
|
<?php if (array_key_exists($columnIdx, $chunk)): ?>
|
||||||
<p class="has-large-font-size" style="color: rgba(100%, 0%, 0%, 0); text-shadow: 0 0 0 green;"><?php echo str_repeat("✔️", $candidate['Rating']); ?></p>
|
<?php
|
||||||
|
$candidate = $chunk[$columnIdx];
|
||||||
|
|
||||||
|
if (isset($candidate['Picture']) && isset($media[$candidate['Picture']])) {
|
||||||
|
$candidate_image = $media[$candidate['Picture']];
|
||||||
|
} else {
|
||||||
|
$candidate_image = $media['default.png'];
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!-- wp:image {"id":<?php echo $candidate_image['id']; ?>,"width":"200px","height":"200px","scale":"cover","align":"center","style":{"color":{}},"className":"is-resized"} -->
|
||||||
|
<figure class="wp-block-image aligncenter is-resized"><img src="<?php echo $candidate_image['url']; ?>" alt="" class="wp-image-<?php echo $candidate_image['id']; ?>" style="object-fit:cover;width:200px;height:200px"/></figure>
|
||||||
|
<!-- /wp:image -->
|
||||||
|
|
||||||
|
<!-- wp:heading {"textAlign":"center","className":"wp-block-heading has-text-align-center has-medium-font-size","style":{"spacing":{"margin":{"top":"1rem"}}}} -->
|
||||||
|
<h2 class="wp-block-heading has-text-align-center has-medium-font-size" style="margin-top:1rem"><strong><?php echo $candidate['Candidate Name']; ?></strong></h2>
|
||||||
|
<!-- /wp:heading -->
|
||||||
|
|
||||||
|
<!-- wp:paragraph {"align":"center","style":{"layout":{"selfStretch":"fit","flexSize":null},"typography":{"lineHeight":"1"},"spacing":{"margin":{"top":"0.5rem","bottom":"1.5rem"}}},"fontSize":"large"} -->
|
||||||
|
<p class="has-text-align-center has-large-font-size" style="margin-top:0.5rem;margin-bottom:1.5rem;line-height:1;color: rgba(100%, 0%, 0%, 0);text-shadow: 0 0 0 green;"><?php echo str_repeat("✔️", $candidate['Rating']); ?></p>
|
||||||
|
<!-- /wp:paragraph -->
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<!-- /wp:column -->
|
||||||
|
<?php endfor; ?>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<!-- /wp:columns -->
|
||||||
|
<?php endforeach; ?>
|
||||||
|
|
||||||
|
<?php else: ?>
|
||||||
|
|
||||||
|
<!-- wp:paragraph -->
|
||||||
|
<p>We don't know who the candidates are for <?php echo $wardName; ?> ward yet.</p>
|
||||||
<!-- /wp:paragraph -->
|
<!-- /wp:paragraph -->
|
||||||
</div>
|
|
||||||
<!-- /wp:group -->
|
|
||||||
<?php endforeach; ?>
|
|
||||||
|
|
||||||
</div>
|
<?php endif; ?>
|
||||||
<!-- /wp:group -->
|
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
|
|
||||||
<?php if (isset($config['footer'])): ?>
|
<?php if (isset($config['footer'])): ?>
|
||||||
|
|||||||
Reference in New Issue
Block a user