Skip to content

Commit

Permalink
Add parking locations (bays) (#242)
Browse files Browse the repository at this point in the history
* Update codespell for pre-commit

* Add parking locations as dataset

* Update the example in documentation
  • Loading branch information
klaasnicolaas authored Dec 2, 2022
1 parent 5f285f3 commit cd3461d
Show file tree
Hide file tree
Showing 10 changed files with 988 additions and 20 deletions.
1 change: 1 addition & 0 deletions .codespell-ignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
bord
1 change: 1 addition & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ repos:
types: [text]
exclude: ^poetry\.lock$
entry: poetry run codespell
args: [--ignore-words=.codespell-ignore]
- id: debug-statements
name: 🪵 Debug Statements and imports (Python)
language: system
Expand Down
33 changes: 30 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!-- Banner -->
![alt Banner of the odp Amsterdam package](https://raw.githubusercontent.com/klaasnicolaas/python-odp-amsterdam/main/assets/header_odp_amsterdam-min.png)
<!-- Header -->
![alt Header of the odp Amsterdam package](https://raw.githubusercontent.com/klaasnicolaas/python-odp-amsterdam/main/assets/header_odp_amsterdam-min.png)

<!-- PROJECT SHIELDS -->
[![GitHub Release][releases-shield]][releases]
Expand Down Expand Up @@ -37,7 +37,8 @@ pip install odp-amsterdam

You can read the following datasets with this package:

- [Parking garages occupancy / Garages parkeerbezetting][garages] (52 garages)
- [Parking garages occupancy / Garages parkeerbezetting][garages] (52 garages)
- [Parking locations / Parkeervakken][parking]

<details>
<summary>Click here to get more details</summary>
Expand All @@ -60,6 +61,23 @@ Read the occupancy of a parking garage in Amsterdam (The Netherlands), both for
| `availability_pct` | float | The percentage of free parking spaces |
| `longitude` | float | The longitude of the garage |
| `latitude` | float | The latitude of the garage |

### Parking locations

You can use the following parameters in your request:

- **limit** (default: 10) - How many results you want to retrieve.
- **parking_type** (default: "") - Filter based on the `eType` from the geojson data.

| Variable | Type | Description |
| :------- | :--- | :---------- |
| `spot_id` | string | The id of the location |
| `spot_type` | string (or None) | The type of the location (e.g. **E6a**) |
| `spot_description` | string (or None) | The description of the location type |
| `street` | string (or None) | The street name of the location |
| `number` | integer (or None) | How many parking spots there are on this location |
| `orientation` | string (or None) | The parking orientation of the location (**visgraag**, **langs** or **file**) |
| `coordinates` | list[float] | The coordinates of the location |
</details>

## Usage
Expand All @@ -73,8 +91,16 @@ from odp_amsterdam import ODPAmsterdam
async def main():
"""Show example on using the ODP Amsterdam API client."""
async with ODPAmsterdam() as client:
# Parking locations
locations: list[ParkingSpot] = await client.location(
limit=5, parking_type="E6a"
)

# Garages
all_garages: list[Garage] = await client.all_garages()
garage: Garage = await client.garage(garage_id="ID_OF_GARAGE")

print(locations)
print(all_garages)
print(garage)

Expand Down Expand Up @@ -172,6 +198,7 @@ SOFTWARE.
[api]: https://api.data.amsterdam.nl
[nipkaart]: https://www.nipkaart.nl
[garages]: https://data.amsterdam.nl/datasets/9ORkef6T-aU29g/actuele-beschikbaarheid-parkeergarages/
[parking]: https://api.data.amsterdam.nl/v1/docs/datasets/parkeervakken.html

<!-- MARKDOWN LINKS & IMAGES -->
[build-shield]: https://github.com/klaasnicolaas/python-odp-amsterdam/actions/workflows/tests.yaml/badge.svg
Expand Down
3 changes: 2 additions & 1 deletion odp_amsterdam/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@
ODPAmsterdamError,
ODPAmsterdamResultsError,
)
from .models import Garage
from .models import Garage, ParkingSpot
from .odp_amsterdam import ODPAmsterdam

__all__ = [
"Garage",
"ParkingSpot",
"ODPAmsterdam",
"ODPAmsterdamError",
"ODPAmsterdamResultsError",
Expand Down
7 changes: 6 additions & 1 deletion odp_amsterdam/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"Fiets",
]

FILTER: list[str] = [
FILTER_NAMES: list[str] = [
"CE-",
"ZD-",
"ZO-",
Expand All @@ -15,6 +15,11 @@
"GRV020HNK ",
]

FILTER_UNKNOWN: list[str] = [
"ONBEKEND",
"Onbekend",
]

CORRECTIONS: list[str] = [
"P1 ",
"P3 ",
Expand Down
59 changes: 55 additions & 4 deletions odp_amsterdam/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,44 @@
from dataclasses import dataclass
from typing import Any

from .const import CORRECTIONS, FILTER
from .const import CORRECTIONS, FILTER_NAMES, FILTER_UNKNOWN


@dataclass
class ParkingSpot:
"""Object representing an ParkingSpot model response from the API."""

spot_id: str
spot_type: str | None
spot_description: str | None

street: str | None
number: int | None
orientation: str | None

coordinates: list[float]

@classmethod
def from_json(cls, data: dict[str, Any]) -> ParkingSpot:
"""Return ParkingSpot object from a dictionary.
Args:
data: The JSON data from the API.
Returns:
An ParkingSpot object.
"""
attr = data["properties"]
regimes = attr["regimes"][0]
return cls(
spot_id=attr["id"],
spot_type=attr["eType"] or None,
spot_description=regimes["eTypeDescription"] or None,
street=filter_unknown(attr["straatnaam"]),
number=int(attr["aantal"]),
orientation=filter_unknown(attr["type"]),
coordinates=data["geometry"]["coordinates"],
)


@dataclass
Expand All @@ -24,13 +61,13 @@ class Garage:

@classmethod
def from_json(cls, data: dict[str, Any]) -> Garage:
"""Return Garages object from a dictionary.
"""Return Garage object from a dictionary.
Args:
data: The JSON data from the API.
Returns:
An Garages object.
An Garage object.
"""
latitude, longitude = split_coordinates(str(data["geometry"]["coordinates"]))
attr = data["properties"]
Expand Down Expand Up @@ -78,11 +115,25 @@ def correct_name(name: str) -> str:
The corrected name.
"""

for value in FILTER:
for value in FILTER_NAMES:
# Remove parts from name string.
name = name.replace(value, "")

if any(y in name for y in CORRECTIONS):
# Add a 0 for consistency.
return name[:1] + "0" + name[1:]
return name


def filter_unknown(data: str) -> str | None:
"""Filter unknown data from the API.
Args:
data: The data to be filtered.
Returns:
The filtered data.
"""
if data in FILTER_UNKNOWN:
return None
return data
35 changes: 29 additions & 6 deletions odp_amsterdam/odp_amsterdam.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@
ODPAmsterdamError,
ODPAmsterdamResultsError,
)
from .models import Garage
from .models import Garage, ParkingSpot


@dataclass
class ODPAmsterdam:
"""Main class for handling data fetchting from Open Data Platform of Amsterdam."""

request_timeout: float = 10.0
request_timeout: float = 15.0
session: aiohttp.client.ClientSession | None = None

_close_session: bool = False
Expand Down Expand Up @@ -56,12 +56,12 @@ async def _request(
the Open Data Platform API of Amsterdam.
"""
version = metadata.version(__package__)
url = URL.build(scheme="http", host="api.data.amsterdam.nl", path="/").join(
url = URL.build(scheme="https", host="api.data.amsterdam.nl", path="/").join(
URL(uri)
)

headers = {
"Accept": "application/json, text/plain",
"Accept": "application/json, text/plain, application/geo+json",
"User-Agent": f"PythonODPAmsterdam/{version}",
}

Expand All @@ -76,7 +76,7 @@ async def _request(
url,
params=params,
headers=headers,
ssl=False,
ssl=True,
)
response.raise_for_status()
except asyncio.TimeoutError as exception:
Expand All @@ -88,8 +88,9 @@ async def _request(
"Error occurred while communicating with the Open Data Platform API."
) from exception

types = ["application/json", "text/plain", "application/geo+json"]
content_type = response.headers.get("Content-Type", "")
if "text/plain" not in content_type:
if not any(item in content_type for item in types):
text = await response.text()
raise ODPAmsterdamError(
"Unexpected content type response from the Open Data Platform API",
Expand All @@ -98,6 +99,28 @@ async def _request(

return json.loads(await response.text())

async def locations(
self, limit: int = 10, parking_type: str = ""
) -> list[ParkingSpot]:
"""Get all the parking locations.
Args:
limit: The number of results to return.
parking_type: The selected parking type number.
Returns:
A list of ParkingSpot objects.
"""
results: list[ParkingSpot] = []
locations = await self._request(
"v1/parkeervakken/parkeervakken",
params={"_pageSize": limit, "eType": parking_type, "_format": "geojson"},
)

for item in locations["features"]:
results.append(ParkingSpot.from_json(item))
return results

async def all_garages(self) -> list[Garage]:
"""Get all the garages.
Expand Down
24 changes: 19 additions & 5 deletions test_output.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,30 @@
async def main() -> None:
"""Show example on using the ODP Amsterdam API client."""
async with ODPAmsterdam() as client:
count: int

garages: list[Garage] = await client.all_garages()
locations = await client.locations(limit=10, parking_type="E6a")
garage = await client.garage(garage_id="900000001_parkinglocation")
count: int

for index, item in enumerate(garages, 1):
print(garages)
print()
print(garage)
print()

for index, item in enumerate(locations, 1):
count = index
print(item)
print(f"{count} parkeergarages gevonden")
print("---")
print(garage)

# # Count unique id's in disabled_parkings
unique_values: list[str] = []
for location in locations:
unique_values.append(location.spot_id)
num_values = len(set(unique_values))

print("__________________________")
print(f"Total locations found: {count}")
print(f"Unique ID values: {num_values}")


if __name__ == "__main__":
Expand Down
Loading

0 comments on commit cd3461d

Please sign in to comment.