add operators

This commit is contained in:
Will Freeman
2024-11-04 23:05:17 -07:00
parent 61d27c1b0f
commit 6c5f95a180
6 changed files with 137 additions and 7 deletions

View File

@@ -14,6 +14,7 @@ const items = [
{ title: 'Map', icon: 'mdi-map', to: '/' },
{ title: 'What is an ALPR?', icon: 'mdi-cctv', to: '/what-is-an-alpr' },
{ title: 'Report an ALPR', icon: 'mdi-map-marker-plus', to: '/report' },
{ title: 'Known Operators', icon: 'mdi-police-badge', to: '/operators' },
{ title: 'About', icon: 'mdi-information', to: '/about' },
{ title: 'Contact', icon: 'mdi-email', to: '/contact' },
{ title: 'Feature Roadmap', icon: 'mdi-road-variant', to: '/roadmap' },

View File

@@ -18,7 +18,7 @@
</v-col>
<v-col>
<h4 class="no-small">
Each Circle represents an Automated License Plate Reader (ALPR).
Each Circle represents an Automated License Plate Reader.
</h4>
</v-col>
</v-row>
@@ -48,7 +48,7 @@
</v-col>
<v-col>
<h4 class="no-small">
Click on an ALPR to view more information about it.
Please check our list of <a href="/operators">Known Operators</a> and report missing ALPRs near you.
</h4>
</v-col>
</v-row>

View File

@@ -36,6 +36,11 @@ const router = createRouter({
name: 'report',
component: () => import('../views/ReportView.vue')
},
{
path: '/operators',
name: 'operators',
component: () => import('../views/OperatorsView.vue')
},
{
path: '/contact',
name: 'contact',

View File

@@ -71,6 +71,12 @@ export const getClusters = async () => {
return response.data;
}
export const getCities = async () => {
const s3Url = "https://deflock-clusters.s3.us-east-1.amazonaws.com/flock_cameras.json";
const response = await apiService.get(s3Url);
return response.data;
}
export const geocodeQuery = async (query: string, currentLocation: any) => {
const encodedQuery = encodeURIComponent(query);
const results = (await apiService.get(`/geocode?query=${encodedQuery}`)).data;

View File

@@ -59,17 +59,20 @@
</template>
</v-text-field>
</form>
<v-btn @click="goToUserLocation" icon class="mt-2">
<v-icon x-large>mdi-crosshairs-gps</v-icon>
</v-btn>
</l-control>
<!-- url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" -->
<l-tile-layer
:url="mapTileUrl"
layer-type="base"
name="OpenStreetMap"
/>
<l-control-zoom position="bottomright" />
<l-control position="bottomright">
<v-btn @click="goToUserLocation" icon class="mt-2">
<v-icon x-large>mdi-crosshairs-gps</v-icon>
</v-btn>
</l-control>
<DFMarkerCluster v-if="showClusters" v-for="cluster in clusters" :key="cluster.id" :lat="cluster.lat" :lon="cluster.lon" />
<DFMapMarker v-else v-for="alpr in visibleALPRs" :key="alpr.id" :alpr :show-fov="zoom >= 16" />
@@ -266,7 +269,7 @@ onMounted(() => {
};
}
} else {
center.value = { lat: 37.855068, lng: -122.357998 };
center.value = { lat: 37.875190, lng: -122.279819 };
}
});

View File

@@ -0,0 +1,115 @@
<template>
<v-container max-width="1000">
<h1>Known Operators</h1>
<p>
We regularly scrape Flock's site for cities/counties that have Flock ALPRs. Here is our current list of jurisdictions we've scraped that have ALPRs. Not every Flock operator has opted in to sharing their usage with Flock, so this list is <i>not exhaustive</i>.
</p>
<v-card class="my-4">
<v-card-text>
<v-select
v-model="selectedState"
:items="distinctStates"
label="Filter by State"
outlined
clearable
/>
<v-data-table
v-model:page="page"
:loading="isLoading"
:headers="headers"
:items="filteredCities"
:items-per-page="8"
>
<template v-slot:item.transparencyReportUrl="i: any">
<v-btn variant="text" :href="i.item.transparencyReportUrl" target="_blank">
<v-icon start>mdi-eye</v-icon> View
</v-btn>
</template>
</v-data-table>
</v-card-text>
</v-card>
<h2>Want to Help?</h2>
<p>
If you live in one of these cities/counties, or if you're bored and want to browse Google Maps Street View, you can help us by reporting the locations of these Flock cameras. Their locations are pretty easy to predict, as they're usually installed at major intersections and the edges of city limits.
</p>
<p class="mb-16">
You can report a camera by following <a href="/report">these instructions</a>.
</p>
</v-container>
</template>
<script setup lang="ts">
import { onMounted, ref, computed, watch } from 'vue';
import { getCities } from '@/services/apiService';
const page = ref(1);
const selectedState = ref('');
const cities = ref([]);
const filteredCities = computed(() => {
if (!selectedState.value) {
return cities.value;
}
return cities.value.filter((city: any) => city.state === selectedState.value);
});
const headers = [
{ title: 'State', value: 'state', sortable: true },
{ title: 'City', value: 'city', sortable: true },
{ title: 'Cameras', value: 'numCameras', sortable: true },
{ title: 'Transparency Report', value: 'transparencyReportUrl', sortable: false },
];
const isLoading = computed(() => cities.value.length === 0);
const distinctStates = computed(() => Array.from(new Set(cities.value.map((city: any) => city.state))).sort());
onMounted(() => {
getCities().then((data: any) => {
cities.value = data;
});
});
watch(selectedState, () => {
page.value = 1;
});
</script>
<style scoped>
/* TODO: put this all in one place, also in what-is view */
h2 {
margin-top: 2rem;
}
h3 {
margin-top: 1.25rem;
}
p {
margin-top: 0.5rem;
}
.flex-image {
display: flex;
gap: 1rem;
align-items: center;
margin-top: 0.5rem;
}
code {
background-color: #f4f4f4;
padding: 0.5rem;
border-radius: 0.25rem;
display: block;
margin-top: 0.5rem;
}
.highlight {
background-color: #0081ac;
padding: 0.15rem;
border-radius: 0.25rem;
font-weight: bold;
}
</style>