Skip to content

Commit

Permalink
feat: initial implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
RoryPTB committed Aug 30, 2024
1 parent 24a5522 commit 011e6cf
Show file tree
Hide file tree
Showing 5 changed files with 380 additions and 0 deletions.
24 changes: 24 additions & 0 deletions src/cap2geojson/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
###############################################################################
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
###############################################################################

from .convert import Converter

__version__ = '0.1.0-dev1'
132 changes: 132 additions & 0 deletions src/cap2geojson/convert.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import math
import xmltodict


def get_properties(alert):
"""Creates the properties object for the GeoJSON Feature object from the CAP alert.
Args:
alert (dict): The extracted CAP alert object.
Returns:
dict: The formatted properties object.
"""
info = alert["cap:info"]
return {
"identifier": alert["cap:identifier"],
"sender": alert["cap:sender"],
"sent": alert["cap:sent"],
"status": alert["cap:status"],
"msgType": alert["cap:msgType"],
"scope": alert["cap:scope"],
"category": info["cap:category"],
"event": info["cap:event"],
"urgency": info["cap:urgency"],
"severity": info["cap:severity"],
"certainty": info["cap:certainty"],
"effective": info["cap:effective"],
"onset": info["cap:onset"],
"expires": info["cap:expires"],
"senderName": info["cap:senderName"],
"headline": info["cap:headline"],
"description": info["cap:description"],
"instruction": info["cap:instruction"],
"web": info["cap:web"],
"contact": info["cap:contact"],
"areaDesc": get_area_desc(info["cap:area"]),
}


def get_area_desc(area):
"""Formats the area description for the GeoJSON properties object.
Args:
area (dict): The area information of the CAP alert.
Returns:
str: The formatted area description.
"""
if isinstance(area, dict):
return area["cap:areaDesc"]
return ", ".join([a["cap:areaDesc"] for a in area])


def get_circle_coord(theta, x_center, y_center, radius):
"""Calculates the x and y coordinates of a point on a circle.
Args:
theta (_type_): _description_
x_center (_type_): _description_
y_center (_type_): _description_
radius (_type_): _description_
Returns:
tuple: The x and y coordinates of the point, rounded to 5dp.
"""
x = radius * math.cos(theta) + x_center
y = radius * math.sin(theta) + y_center
return (round(x, 5), round(y, 5))


def get_all_circle_coords(x_center, y_center, radius, n_points):
"""
Estimate the n coordinates of a circle with a given center and radius.
Args:
x_center (_type_): _description_
y_center (_type_): _description_
radius (_type_): _description_
n_points (_type_): _description_
Returns:
list: The n estimated coordinates of the circle.
"""
thetas = [i / n_points * math.tau for i in range(n_points)]
circle_coords = [
get_circle_coord(theta, x_center, y_center, radius) for theta in thetas
]
return circle_coords


def get_multi_coordinates(area):
"""Formats the coordinates for the GeoJSON MultiPolygon object.
Args:
area (dict): The area information of the CAP alert.
Returns:
list: The formatted multi-polygon coordinates.
"""
# Idea: loop over each area object and check if it's "cap:circle" or "cap:polygon"
# If it's a circle, calculate the circle coordinates and add them to the list
# If it's a polygon, add the polygon coordinates to the list


def to_geojson(xml):
"""Takes the CAP alert XML and converts it to a GeoJSON.
Args:
xml (bytes): The CAP XML byte string.
Returns:
dict: The final GeoJSON object.
"""
data = xmltodict.parse(xml)
alert = data["cap:alert"]

alert_properties = get_properties(alert)
multi_polygon_coordinates = get_multi_coordinates(alert)

return {
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": alert_properties,
"geometry": {
"type": "MultiPolygon",
"coordinates": [multi_polygon_coordinates],
},
}
],
}
72 changes: 72 additions & 0 deletions tests/data/bf.xml

Large diffs are not rendered by default.

71 changes: 71 additions & 0 deletions tests/data/sc.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?xml version='1.0' encoding='utf-8'?>
<?xml-stylesheet type="text/xsl" href="https://www.meteo.sc/cap-alert-style.xsl"?>
<alert xmlns="urn:oasis:names:tc:emergency:cap:1.2">
<identifier>urn:oid:2.49.0.0.690.0.2024.5.19.13.18.0</identifier>
<sender>[email protected]</sender>
<sent>2024-05-19T17:18:00+04:00</sent>
<status>Actual</status>
<msgType>Alert</msgType>
<scope>Public</scope>
<info>
<language>en</language>
<category>Met</category>
<event>Strong Winds</event>
<urgency>Immediate</urgency>
<severity>Moderate</severity>
<certainty>Observed</certainty>
<audience>General Public</audience>
<effective>2024-05-19T17:30:00+04:00</effective>
<onset>2024-05-19T17:30:00+04:00</onset>
<expires>2024-05-19T23:30:00+04:00</expires>
<senderName>Seychelles Meteorological Authority</senderName>
<headline>Moderate to strong south-easterly winds over Aldabra area</headline>
<description>Moderate to strong south-easterly winds over Aldabra area associated with a severe tropical storm Ialy on 19th May 2024 from 5pm to 11pm</description>
<instruction>Beware of strong south-easterly winds of 40km/hr gusting to 60km/hr causing rough seas. Mariners are advised to take extra precautions when navigating these areas</instruction>
<web>https://www.meteo.sc/alerts/severe-tropical-storm-ialy-is-expected-to-cause-moderate-to-strong-south-easterly-winds-over-aldabra-area-on-19th-may-2024/</web>
<contact>[email protected]</contact>
<area>
<areaDesc>Aldabra area</areaDesc>
<polygon>-9.186965,46.078276 -9.336746,45.936002 -9.57065,45.983427 -9.748309,46.116216 -9.897844,46.249006 -10.000609,46.42922 -9.953902,46.694799 -9.692216,46.770679 -9.467752,46.770679 -9.327387,46.742224 -9.186965,46.694799 -9.140146,46.533555 -9.121416,46.362825 -9.186965,46.078276</polygon>
</area>
</info>
<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2006/12/xml-c14n11"/>
<ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256"/>
<ds:Reference URI="">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
<ds:Transform Algorithm="http://www.w3.org/2006/12/xml-c14n11"/>
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<ds:DigestValue>s9RKcAph3khDLX4nOQQDZ7c23uVZqCDJziRZznnh3nA=</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>liScT3sHEpNO1TXEDY1nMC5FoBnQruioH/xkU1rNRKfK8Y3y4/lRz64ueJfoWGYB/N3NWLkMhQ6V0X4lHGPyfA==</ds:SignatureValue>
<ds:KeyInfo>
<ds:X509Data>
<ds:X509Certificate>MIIDeTCCAv6gAwIBAgISA7FZkY9qfwdIeZgOZUlggQr+MAoGCCqGSM49BAMDMDIx
CzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQDEwJF
NTAeFw0yNDA3MDIxMTIzNDlaFw0yNDA5MzAxMTIzNDhaMBcxFTATBgNVBAMTDHd3
dy5tZXRlby5zYzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABPnwJK7C7LIL7c3L
DXDTbFlLSHx8VXbbSKFqkr0v8xjseuNjh8IXFm95mvkdk7q1S0SUYyYn3d8a0krt
2qhGqCKjggINMIICCTAOBgNVHQ8BAf8EBAMCB4AwHQYDVR0lBBYwFAYIKwYBBQUH
AwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFCQFyLRIaHFpqT8I
nTKhv8oSaTW1MB8GA1UdIwQYMBaAFJ8rX888IU+dBLftKyzExnCL0tcNMFUGCCsG
AQUFBwEBBEkwRzAhBggrBgEFBQcwAYYVaHR0cDovL2U1Lm8ubGVuY3Iub3JnMCIG
CCsGAQUFBzAChhZodHRwOi8vZTUuaS5sZW5jci5vcmcvMBcGA1UdEQQQMA6CDHd3
dy5tZXRlby5zYzATBgNVHSAEDDAKMAgGBmeBDAECATCCAQMGCisGAQQB1nkCBAIE
gfQEgfEA7wB2AHb/iD8KtvuVUcJhzPWHujS0pM27KdxoQgqf5mdMWjp0AAABkHNm
tr8AAAQDAEcwRQIhANX48FhFLRl8W0qsVh12vz2F92wr2aKId+AQ/0kvE+a0AiA/
eB4KudtHm4LJL7VSVL7UvffuEPOsY+PvoongycjZpQB1AEiw42vapkc0D+VqAvqd
MOscUgHLVt0sgdm7v6s52IRzAAABkHNmvkUAAAQDAEYwRAIgFtFOST10XUPf2BYT
xBBvHVqU98eB2hwQtgVJ4hJP5RoCIF0wDotvI7r+kamXqgvee+/ig4NP2ZbqaLP6
a2/T5cjnMAoGCCqGSM49BAMDA2kAMGYCMQCyLgDsI/yPYKkI1zM3zs0w7iI23MfZ
BGuNKUUa7qHLR1O6eNnEmrSH24bdzXdacRoCMQCLW6bf0Y1mwuJN+jBCjTbyCe+F
1ZEDJBb2AKxTZpWdVdtfRErY5BxHuACOm9SlXGE=
</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</ds:Signature>
</alert>
Loading

0 comments on commit 011e6cf

Please sign in to comment.