Skip to content
This repository has been archived by the owner on Jun 1, 2022. It is now read-only.

Commit

Permalink
Initial /api/export-mapbox/Locations.geojson API, refs #285
Browse files Browse the repository at this point in the history
  • Loading branch information
simonw committed Apr 15, 2021
1 parent e7cd223 commit c340bd2
Show file tree
Hide file tree
Showing 6 changed files with 117 additions and 15 deletions.
11 changes: 11 additions & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -418,3 +418,14 @@ Examples:
- https://vial-staging.calltheshots.us/api/counties/CA
- https://vial-staging.calltheshots.us/api/counties/OR
- https://vial-staging.calltheshots.us/api/counties/RI

## GET /api/export-mapbox/Locations.geojson

This returns a GeoJSON file for use with Mapbox. This streams out records for ALL of our locations, so it can be very large!

You can control which locations are returned (useful for debugging) with the following parameters:

- `?limit=10` - only return 10 locations
- `?id=recXXX&id=lxx` - just return GeoJSON for specific location IDs (multiple allowed)

Try this API at https://vial-staging.calltheshots.us/api/export-mapbox/Locations.geojson?limit=10
28 changes: 28 additions & 0 deletions vaccinate/api/test_mapbox_export.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import json

import pytest


@pytest.mark.django_db
def test_mapbox_export(client, ten_locations):
response = client.get("/api/export-mapbox/Locations.geojson")
joined = b"".join(response.streaming_content)
assert json.loads(joined) == {
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {
"id": location.public_id,
"name": location.name,
"location_type": "Hospital / Clinic",
"website": None,
"address": None,
"hours": None,
"public_notes": None,
},
"geometry": {"type": "Point", "coordinates": [40.0, 30.0]},
}
for location in ten_locations
],
}
60 changes: 59 additions & 1 deletion vaccinate/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import os
import pathlib
import random
import textwrap
from datetime import datetime, timedelta
from typing import List, Optional

Expand Down Expand Up @@ -32,10 +33,11 @@
Reporter,
State,
)
from core.utils import keyset_pagination_iterator
from dateutil import parser
from django.conf import settings
from django.db import transaction
from django.http import JsonResponse
from django.http import JsonResponse, StreamingHttpResponse
from django.shortcuts import render
from django.utils import timezone
from django.utils.timezone import localdate
Expand Down Expand Up @@ -771,3 +773,59 @@ def api_export_preview_locations(request):
@beeline.traced(name="location_metrics")
def location_metrics(request):
return LocationMetricsReport().serve()


@beeline.traced(name="export_mapbox_geojson")
def export_mapbox_geojson(request):
locations = Location.objects.all().select_related(
"location_type", "dn_latest_non_skip_report"
)
location_ids = request.GET.getlist("id")
if location_ids:
locations = locations.filter(public_id__in=location_ids)
limit = None
if request.GET.get("limit", "").isdigit():
limit = int(request.GET["limit"])
start = textwrap.dedent(
"""
{
"type": "FeatureCollection",
"features": [
"""
)

def chunks():
yield start
started = False
for location in keyset_pagination_iterator(locations, stop_after=limit):
if started:
yield ","
started = True
yield json.dumps(
{
"type": "Feature",
"properties": {
"id": location.public_id,
"name": location.name,
"location_type": location.location_type.name,
"website": location.website,
"address": location.full_address,
# "provider": "County",
# "appointment_information": "",
# "date_added": "2021-01-15T21:49:00.000Z",
# "last_contacted_date": "2021-04-14T16:07:00.000Z",
# "vaccines_offered": [],
"hours": location.hours,
"public_notes": location.dn_latest_non_skip_report.public_notes
if location.dn_latest_non_skip_report
else None,
},
"geometry": {
"type": "Point",
"coordinates": [location.longitude, location.latitude],
},
}
)
yield "]}"

return StreamingHttpResponse(chunks(), content_type="application/json")
1 change: 1 addition & 0 deletions vaccinate/config/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
path("api/availabilityTags", api_views.availability_tags),
path("api/export", api_views.api_export),
path("api/export-preview/Locations.json", api_views.api_export_preview_locations),
path("api/export-mapbox/Locations.geojson", api_views.export_mapbox_geojson),
path("api/location_metrics", api_views.location_metrics),
path("api/counties/<state_abbreviation>", api_views.counties),
path("", include("django.contrib.auth.urls")),
Expand Down
15 changes: 1 addition & 14 deletions vaccinate/core/admin_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,7 @@
from django.db.models.fields.related import ForeignKey
from django.http import StreamingHttpResponse


def keyset_pagination_iterator(input_queryset, batch_size=500):
all_queryset = input_queryset.order_by("pk")
last_pk = None
while True:
queryset = all_queryset
if last_pk is not None:
queryset = all_queryset.filter(pk__gt=last_pk)
queryset = queryset[:batch_size]
for row in queryset:
last_pk = row.pk
yield row
if not queryset:
break
from .utils import keyset_pagination_iterator


def export_as_csv_action(
Expand Down
17 changes: 17 additions & 0 deletions vaccinate/core/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
def keyset_pagination_iterator(input_queryset, batch_size=500, stop_after=None):
all_queryset = input_queryset.order_by("pk")
last_pk = None
i = 0
while True:
queryset = all_queryset
if last_pk is not None:
queryset = all_queryset.filter(pk__gt=last_pk)
queryset = queryset[:batch_size]
for row in queryset:
last_pk = row.pk
yield row
i += 1
if stop_after and i >= stop_after:
return
if not queryset:
break

0 comments on commit c340bd2

Please sign in to comment.