Skip to content

Commit

Permalink
Merge pull request #1463 from weather-gov/mgwalker/1409-hazard-counties
Browse files Browse the repository at this point in the history
Fixes a bug with how we load forecast and fire zones from shapefile
  • Loading branch information
greg-does-weather authored Jul 25, 2024
2 parents 75f76b7 + a9357ec commit f7ccf75
Show file tree
Hide file tree
Showing 6 changed files with 80 additions and 32 deletions.
2 changes: 1 addition & 1 deletion .github/actions/populate-database/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ runs:
uses: actions/cache@v4
id: db-cache
with:
key: drupal-database-${{ hashFiles('web/config/**/*.yml', 'web/scs-export/**/*', 'web/modules/weather_i18n/translations/*.po') }}
key: drupal-database-${{ hashFiles('web/config/**/*.yml', 'web/scs-export/**/*', 'web/modules/weather_i18n/translations/*.po', 'spatial/**/*.js') }}
path: weathergov.sql

- name: setup image cacheing
Expand Down
2 changes: 1 addition & 1 deletion .github/actions/setup-site/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ runs:
uses: actions/cache@v4
id: db-cache
with:
key: drupal-database-${{ hashFiles('web/config/**/*.yml', 'web/scs-export/**/*', 'web/modules/weather_i18n/translations/*.po') }}
key: drupal-database-${{ hashFiles('web/config/**/*.yml', 'web/scs-export/**/*', 'web/modules/weather_i18n/translations/*.po', 'spatial/**/*.js') }}
path: weathergov.sql

- name: start the site
Expand Down
4 changes: 3 additions & 1 deletion spatial-data/lib/prep.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,7 @@ module.exports.downloadAndUnzip = async (url) => {

module.exports.unzip = async (path) => {
console.log(` [${path}] decompressing...`);
await exec(`unzip -u ${path}`);

// Use -o to overwrite existing files.
await exec(`unzip -o -u ${path}`);
};
65 changes: 44 additions & 21 deletions spatial-data/sources/zones.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
const fs = require("node:fs/promises");
const shapefile = require("shapefile");

const { dropIndexIfExists, openDatabase } = require("../lib/db.js");

const metadata = {
table: "weathergov_geo_zones",
version: 1,
version: 2,
};

module.exports = async () => {
Expand All @@ -24,6 +25,13 @@ module.exports = async () => {
await dropIndexIfExists(db, "zones_spatial_idx", metadata.table);
await db.query(`TRUNCATE TABLE ${metadata.table}`);

// Version 2: Change the shape column into a collection rather than a single
// multipolygon. This allows us to capture all of the polygons for a zone as
// a collection rather than trying to collect or union them into one entity.
await db.query(
`ALTER TABLE ${metadata.table} MODIFY shape GEOMETRYCOLLECTION`,
);

const found = new Map();

const processFile = async (filename, zoneType) => {
Expand All @@ -39,30 +47,23 @@ module.exports = async () => {
geometry,
} = value;

if (geometry.type === "Polygon") {
geometry.type = "MultiPolygon";
geometry.coordinates = [geometry.coordinates];
}
// These shapefiles are in NAD83, whose SRID is 4269.
geometry.crs = { type: "name", properties: { name: "EPSG:4269" } };

const id = `https://api.weather.gov/zones/${zoneType}/${state}Z${zone}`;

// Some of the zones are duplicated. Dunno why. Don't put them in twice,
// the database will scream.
// Some of the zones are represented by multiple polygons. To handle that,
// we'll gather a list of all polygons and insert them into the database
// as a geometry collection.
if (!found.has(id)) {
found.set(id, { state, zone, zoneType, filename, geometry });

await db.query(
`INSERT INTO weathergov_geo_zones
(id, state, shape)
VALUES(
'${id}',
'${state}',
ST_GeomFromGeoJSON('${JSON.stringify(geometry)}')
)`,
);
found.set(id, {
state,
zone,
zoneType,
filename,
geometry: [geometry],
});
} else {
found.get(id).geometry.push(geometry);
}

return file.read().then(getSqlForShape);
};

Expand All @@ -72,6 +73,28 @@ module.exports = async () => {
await processFile(`./z_05mr24.shp`, "forecast");
await processFile(`./fz05mr24.shp`, "fire");

// Our map now contains entries for every zone. Iterate over that to insert
// them into the database.
for await (const [id, { state, geometry }] of found) {
const featureCollection = {
type: "FeatureCollection",
features: geometry,
// Shapefiles are in NAD83, whose SRID is 4269. Set that at the collection
// level so that it automatically applies to all contained shapes.
crs: { type: "name", properties: { name: "EPSG:4269" } },
};

await db.query(
`INSERT INTO ${metadata.table}
(id, state, shape)
VALUES(
'${id}',
'${state}',
ST_GeomFromGeoJSON('${JSON.stringify(featureCollection)}')
)`,
);
}

db.end();
};

Expand Down
14 changes: 11 additions & 3 deletions web/modules/weather_data/src/Service/AlertUtility.php
Original file line number Diff line number Diff line change
Expand Up @@ -464,9 +464,17 @@ public static function getGeometryAsJSON($alert, $dataLayer)

$polygon = json_decode($polygon->shape);

$polygon->coordinates = SpatialUtility::swapLatLon(
$polygon->coordinates,
);
if ($polygon->type == "GeometryCollection") {
foreach ($polygon->geometries as $innerPolygon) {
$innerPolygon->coordinates = SpatialUtility::swapLatLon(
$innerPolygon->coordinates,
);
}
} else {
$polygon->coordinates = SpatialUtility::swapLatLon(
$polygon->coordinates,
);
}
}

return $polygon;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,20 +88,29 @@ final class AlertUtilityGeometryTest extends TestCase
(object) ["shape" => "union 1,2,3"],
],

// Return the simplified shape
// Return the simplified shape as a geometry collection, so we
// can also test that those points are flipped properly.
[
"SELECT ST_ASGEOJSON(
ST_SIMPLIFY(
ST_GEOMFROMTEXT('union 1,2,3'),
0.003
)
) as shape",
(object) ["shape" => '{"coordinates":[[0,1],[2,3],[4,5]]}'],
(object) [
"shape" =>
'{"type":"GeometryCollection","geometries":[{"coordinates":[[0,1],[2,3],[4,5]]}]}',
],
],
]),
);

$expected = (object) ["coordinates" => [[1, 0], [3, 2], [5, 4]]];
$expected = (object) [
"type" => "GeometryCollection",
"geometries" => [
(object) ["coordinates" => [[1, 0], [3, 2], [5, 4]]],
],
];

$actual = AlertUtility::getGeometryAsJSON($alert, $this->dataLayer);
$this->assertEquals($expected, $actual);
Expand Down Expand Up @@ -176,12 +185,18 @@ final class AlertUtilityGeometryTest extends TestCase
0.003
)
) as shape",
(object) ["shape" => '{"coordinates":[[0,1],[2,3],[4,5]]}'],
(object) [
"shape" =>
'{"type":"Polygon","coordinates":[[0,1],[2,3],[4,5]]}',
],
],
]),
);

$expected = (object) ["coordinates" => [[1, 0], [3, 2], [5, 4]]];
$expected = (object) [
"type" => "Polygon",
"coordinates" => [[1, 0], [3, 2], [5, 4]],
];

$actual = AlertUtility::getGeometryAsJSON($alert, $this->dataLayer);
$this->assertEquals($expected, $actual);
Expand Down

0 comments on commit f7ccf75

Please sign in to comment.