a lot of refactoring and cleaning up

This commit is contained in:
Will Freeman
2024-12-27 20:22:04 -07:00
parent 75e12f43a4
commit 1ba3f9c3bb
15 changed files with 216 additions and 320 deletions

View File

@@ -57,9 +57,9 @@ watch(() => theme.global.name.value, (newTheme) => {
<v-spacer></v-spacer>
<v-btn icon>
<!-- <v-btn icon>
<v-icon @click="toggleTheme">mdi-theme-light-dark</v-icon>
</v-btn>
</v-btn> -->
</v-app-bar>
<v-navigation-drawer

View File

@@ -31,3 +31,8 @@ p {
.serif {
font-family: "PT Serif", serif;
}
/* Prevent Scrolling Horizontally */
html, body {
overflow-x: hidden;
}

View File

@@ -13,7 +13,7 @@ h1, h2, h3, h4, h5, h6 {
}
p {
margin: 0.5em 0;
margin: 0.8em 0;
}
ul, ol {

View File

@@ -1,6 +1,6 @@
<template>
<div class="counter">
<span ref="counterEl" class="font-weight-bold">0</span>
<span :class="{ mobile: isMobile }" ref="counterEl" class="font-weight-bold">0</span>
<span class="caption">&nbsp;ALPRs Reported Worldwide</span>
<div :class="{ 'fade-in': showFinalAnimation }" class="subheading fade-text">and rapidly growing!</div>
</div>
@@ -8,6 +8,7 @@
<script setup lang="ts">
import { onMounted, ref, watch, type Ref } from 'vue';
import { useDisplay } from 'vuetify'
import { getALPRCounts } from '@/services/apiService';
import { CountUp } from 'countup.js';
@@ -30,6 +31,7 @@ const counts: Ref<Counts> = ref({
worldwide: undefined,
});
const showFinalAnimation = ref(false);
const { xs: isMobile } = useDisplay();
onMounted(() => {
getALPRCounts().then((response) => {
@@ -74,4 +76,9 @@ watch(counts, (newCounts: Counts) => {
.fade-in {
opacity: 1;
}
.mobile {
display: block;
font-size: 1.2em;
}
</style>

View File

@@ -14,7 +14,7 @@
<v-icon start>mdi-compass-outline</v-icon> <b>{{ cardinalDirection }}</b>
</v-list-item>
<v-list-item>
<v-icon start>mdi-factory</v-icon> <b>
<v-icon start>mdi-domain</v-icon> <b>
<span v-if="alpr.tags.manufacturer">
{{ alpr.tags.manufacturer }}
</span>
@@ -27,7 +27,7 @@
</b>
</v-list-item>
<v-list-item>
<v-icon start>mdi-police-badge</v-icon> <b>
<v-icon start>mdi-account-tie</v-icon> <b>
<span v-if="alpr.tags.operator">
{{ alpr.tags.operator }}
</span>
@@ -39,7 +39,12 @@
</v-list>
<div class="text-center text-grey-darken-1">
node/{{ alpr.id }}
<v-tooltip open-delay="500" text="OSM Node ID" location="bottom">
<template #activator="{ props }">
<span style="cursor: default" v-bind="props">node/{{ alpr.id }}</span>
</template>
</v-tooltip>
</div>
</v-sheet>
</template>
@@ -48,7 +53,7 @@
import { defineProps, computed } from 'vue';
import type { PropType } from 'vue';
import type { ALPR } from '@/types';
import { VIcon, VList, VSheet, VListItem, VChip } from 'vuetify/components';
import { VIcon, VList, VSheet, VListItem, VTooltip } from 'vuetify/components';
const props = defineProps({
alpr: {
@@ -60,11 +65,11 @@ const props = defineProps({
const isFaceRecognition = computed(() => props.alpr.tags.brand === 'Avigilon');
const cardinalDirection = computed(() =>
props.alpr.tags.direction === undefined ? 'Unknown' : degreesToCardinal(parseInt(props.alpr.tags.direction))
props.alpr.tags.direction === undefined ? 'Unknown Direction' : degreesToCardinal(parseInt(props.alpr.tags.direction))
);
function degreesToCardinal(degrees: number): string {
const cardinals = ['North', 'Northeast', 'East', 'Southeast', 'South', 'Southwest', 'West', 'Northwest'];
return cardinals[Math.round(degrees / 45) % 8];
return 'Faces ' + cardinals[Math.round(degrees / 45) % 8];
}
</script>

View File

@@ -11,23 +11,25 @@
</template>
<script setup lang="ts">
import { onMounted, h, createApp, watch, ref, type PropType, type Ref } from 'vue';
import L, { type LatLngExpression } from 'leaflet';
import { onBeforeUnmount, onMounted, h, createApp, watch, ref, type PropType, type Ref } from 'vue';
import L, { type LatLngExpression, type FeatureGroup, type MarkerClusterGroup, type Marker, type CircleMarker } from 'leaflet';
import type { ALPR } from '@/types';
import DFMapPopup from './DFMapPopup.vue';
import { createVuetify } from 'vuetify'
import 'leaflet/dist/leaflet.css';
import 'leaflet.markercluster';
import 'leaflet.markercluster/dist/MarkerCluster.Default.css';
import 'leaflet.markercluster/dist/MarkerCluster.css';
import { createVuetify } from 'vuetify'
// Color of Marker Circle
const MARKER_COLOR = 'rgb(63,84,243)';
// Internal State Management
const markerMap = new Map<string, Marker | CircleMarker>();
const isInternalUpdate = ref(false);
const props = defineProps({
center: {
type: Object,
required: true,
},
zoom: {
@@ -44,59 +46,22 @@ const props = defineProps({
},
});
const emit = defineEmits(['update:center', 'update:zoom', 'update:bounds']);
// Map instance and layers
let map: L.Map;
let circlesLayer: L.FeatureGroup;
let clusterLayer: L.MarkerClusterGroup;
let currentLocationLayer: L.FeatureGroup;
let circlesLayer: FeatureGroup;
let clusterLayer: MarkerClusterGroup;
let currentLocationLayer: FeatureGroup;
onMounted(() => {
initializeMap();
});
function initializeMap() {
map = L.map('map', { zoomControl: false, maxZoom: 18 /* max for OSM tiles */ }).setView(props.center, props.zoom);
registerEvents();
registerWatchers();
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
}).addTo(map);
emit('update:bounds', map.getBounds()); // XXX: this event populates the map
}
function renderCurrentLocation() {
if (!props.currentLocation)
return;
if (currentLocationLayer) {
map.removeLayer(currentLocationLayer);
}
currentLocationLayer = L.featureGroup();
const clMarker = L.circleMarker([props.currentLocation[0], props.currentLocation[1]], {
radius: 10,
color: '#ffffff',
fillColor: '#007bff',
fillOpacity: 1,
weight: 4
});
clMarker.bindPopup('Current Location');
currentLocationLayer.addLayer(clMarker);
map.addLayer(currentLocationLayer);
}
function makeSVGMarker(alpr: ALPR): L.Marker {
const { lat, lon: lng } = alpr;
// Marker Creation Utilities
function createSVGMarker(alpr: ALPR): string {
const orientationDegrees = alpr.tags.direction;
const fovPath = `
<path class="someSVGpath" d="M215.248,221.461L99.696,43.732C144.935,16.031 198.536,0 256,0C313.464,0 367.065,16.031 412.304,43.732L296.752,221.461C287.138,209.593 272.448,202.001 256,202.001C239.552,202.001 224.862,209.593 215.248,221.461Z" style="fill:rgb(87,87,87);fill-opacity:0.46;"/>
<path class="someSVGpath" d="M215.248,221.461L99.696,43.732C144.935,16.031 198.536,0 256,0C313.464,0 367.065,16.031 412.304,43.732L296.752,221.461C287.138,209.593 272.448,202.001 256,202.001C239.552,202.001 224.862,209.593 215.248,221.461ZM217.92,200.242C228.694,192.652 241.831,188.195 256,188.195C270.169,188.195 283.306,192.652 294.08,200.242C294.08,200.242 392.803,48.4 392.803,48.4C352.363,26.364 305.694,13.806 256,13.806C206.306,13.806 159.637,26.364 119.197,48.4L217.92,200.242Z" style="fill:rgb(137,135,135);"/>
`;
const svgMarker = `
return `
<svg style="transform:rotate(${orientationDegrees}deg)" class="svgMarker" width="100%" height="100%" viewBox="0 0 512 512" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
${orientationDegrees ? fovPath : ''}
<g transform="matrix(0.906623,0,0,0.906623,23.9045,22.3271)">
@@ -105,31 +70,21 @@ function makeSVGMarker(alpr: ALPR): L.Marker {
</g>
</svg>
`;
const el = document.createElement('div');
el.innerHTML = svgMarker;
el.style.width = '50px';
el.style.height = '50px';
const icon = L.divIcon({
className: 'leaflet-data-marker',
html: svgMarker,
iconSize: [60, 60],
iconAnchor: [30, 30],
popupAnchor: [0, 0],
});
const marker = L.marker([lat, lng], { icon: icon });
const markerWithPopup = bindPopup(marker, alpr);
return markerWithPopup as L.Marker;
}
function makeCircleMarker(alpr: ALPR): L.CircleMarker {
const { lat, lon: lng } = alpr;
const orientationDegrees = alpr.tags.direction;
function createMarker(alpr: ALPR): Marker | CircleMarker {
if (hasPlottableOrientation(alpr.tags.direction)) {
const icon = L.divIcon({
className: 'leaflet-data-marker',
html: createSVGMarker(alpr),
iconSize: [60, 60],
iconAnchor: [30, 30],
popupAnchor: [0, 0],
});
return L.marker([alpr.lat, alpr.lon], { icon });
}
const marker = L.circleMarker([lat, lng], {
return L.circleMarker([alpr.lat, alpr.lon], {
fill: true,
fillColor: MARKER_COLOR,
fillOpacity: 0.6,
@@ -139,9 +94,6 @@ function makeCircleMarker(alpr: ALPR): L.CircleMarker {
radius: 8,
weight: 3,
});
const markerWithPopup = bindPopup(marker, alpr);
return markerWithPopup as L.CircleMarker;
}
function bindPopup(marker: L.CircleMarker | L.Marker, alpr: ALPR): L.CircleMarker | L.Marker {
@@ -151,13 +103,15 @@ function bindPopup(marker: L.CircleMarker | L.Marker, alpr: ALPR): L.CircleMarke
const popupContent = document.createElement('div');
createApp({
render() {
return h(DFMapPopup, { alpr: {
id: alpr.id,
lat: alpr.lat,
lon: alpr.lon,
tags: alpr.tags,
type: alpr.type,
} });
return h(DFMapPopup, {
alpr: {
id: alpr.id,
lat: alpr.lat,
lon: alpr.lon,
tags: alpr.tags,
type: alpr.type,
}
});
}
}).use(createVuetify()).mount(popupContent);
@@ -172,72 +126,124 @@ function hasPlottableOrientation(orientationDegrees: string) {
return orientationDegrees && !isNaN(parseInt(orientationDegrees));
}
function populateMap() {
const showFov = props.zoom >= 16;
// Map State Management
function initializeMap() {
map = L.map('map', {
zoomControl: false,
maxZoom: 18, // max for OSM tiles
minZoom: 3, // don't overload the browser
}).setView(props.center, props.zoom);
renderCurrentLocation();
if (clusterLayer) {
map.removeLayer(clusterLayer);
}
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
}).addTo(map);
clusterLayer = L.markerClusterGroup({
chunkedLoading: true,
disableClusteringAtZoom: 16, // showFov threshold
disableClusteringAtZoom: 16,
removeOutsideVisibleBounds: true,
maxClusterRadius: 60,
zoomToBoundsOnClick: true,
spiderfyOnEveryZoom: false,
spiderfyOnMaxZoom: false,
});
circlesLayer = L.featureGroup();
currentLocationLayer = L.featureGroup();
for (const alpr of props.alprs) {
const orientationDegrees = alpr.tags.direction;
map.addLayer(clusterLayer);
registerMapEvents();
let marker: L.CircleMarker | L.Marker;
if (props.alprs.length) {
updateMarkers(props.alprs);
} else {
emit('update:bounds', map.getBounds());
}
}
if (hasPlottableOrientation(orientationDegrees)) {
marker = makeSVGMarker(alpr);
} else {
marker = makeCircleMarker(alpr);
function updateMarkers(newAlprs: ALPR[]): void {
const currentIds = new Set(markerMap.keys());
const nonexistingAlprs = newAlprs.filter(alpr => !currentIds.has(alpr.id));
// Add markers
for (const alpr of nonexistingAlprs) {
if (!currentIds.has(alpr.id)) {
// Add new marker
const marker = createMarker(alpr);
bindPopup(marker, alpr);
markerMap.set(alpr.id, marker);
circlesLayer.addLayer(marker);
}
circlesLayer.addLayer(marker);
}
// Update cluster layer
clusterLayer.clearLayers();
clusterLayer.addLayer(circlesLayer);
map.addLayer(clusterLayer);
}
const emit = defineEmits(['update:center', 'update:zoom', 'update:bounds']);
function updateCurrentLocation(): void {
currentLocationLayer.clearLayers();
function registerEvents() {
map.on('moveend', () => {
emit('update:center', map.getCenter());
emit('update:zoom', map.getZoom());
emit('update:bounds', map.getBounds());
});
if (props.currentLocation) {
const marker = L.circleMarker([props.currentLocation[0], props.currentLocation[1]], {
radius: 10,
color: '#ffffff',
fillColor: '#007bff',
fillOpacity: 1,
weight: 4
}).bindPopup('Current Location');
currentLocationLayer.addLayer(marker);
map.addLayer(currentLocationLayer);
}
}
function registerWatchers() {
watch(() => props.center, (newCenter: any, oldCenter: any) => {
if (newCenter.lat !== oldCenter.lat || newCenter.lng !== oldCenter.lng) {
map.setView(newCenter as LatLngExpression);
// Lifecycle Hooks
onMounted(() => {
initializeMap();
// Watch for prop changes
watch(() => props.center, (newCenter: any) => {
if (!isInternalUpdate.value) {
isInternalUpdate.value = true;
map.setView(newCenter, map.getZoom(), { animate: false });
setTimeout(() => {
isInternalUpdate.value = false;
}, 0);
}
});
watch(() => props.currentLocation, (newLocation, oldLocation) => {
console.log("current location watcher triggered!");
renderCurrentLocation();
watch(() => props.zoom, (newZoom: number) => {
if (!isInternalUpdate.value) {
isInternalUpdate.value = true;
map.setZoom(newZoom);
setTimeout(() => {
isInternalUpdate.value = false;
}, 0);
}
});
watch(() => props.alprs, (newAlprs, oldAlprs) => {
populateMap();
});
watch(() => props.alprs, (newAlprs) => {
updateMarkers(newAlprs);
}, { deep: true });
watch(() => props.currentLocation, () => {
updateCurrentLocation();
});
});
onBeforeUnmount(() => {
map?.remove();
});
function registerMapEvents() {
map.on('moveend', () => {
if (!isInternalUpdate.value) {
emit('update:center', map.getCenter());
emit('update:zoom', map.getZoom());
emit('update:bounds', map.getBounds());
}
});
}
</script>
<style scoped>

View File

@@ -8,8 +8,8 @@
:items="alprBrands"
item-title="nickname"
return-object
label="Manufacturer"
variant="solo-filled"
label="Choose a Manufacturer"
variant="outlined"
flat
hide-details
></v-select>

View File

@@ -57,6 +57,7 @@
<div>
<p>&copy; {{ currentYear }} DeFlock. All Rights Reserved</p>
<p>Map data © <a href="https://www.openstreetmap.org/copyright" target="_blank" style="color: unset; font-weight: normal;">OpenStreetMap contributors</a></p>
<p class="mt-4">v1.0</p>
</div>
</v-col>
</v-row>

View File

@@ -101,7 +101,7 @@ const router = createRouter({
{
path: '/qr',
name: 'qr-landing',
component: () => import('../views/QRLandingView.vue'),
component: () => import('../views/Landing.vue'),
meta: {
title: 'You Found an ALPR | DeFlock'
}

View File

@@ -16,7 +16,7 @@
<v-col cols="12" md="10">
<h2 class="mb-2">Our Amazing Sponsors</h2>
<p class="mb-8">
Want to see your name here? <a href="https://github.com/sponsors/frillweeman">Become a sponsor</a>, and your name will appear on this page!
Want to see your name here? <a target="_blank" href="https://github.com/sponsors/frillweeman">Become a sponsor</a>, and your name will appear on this page!
</p>
<v-row>

View File

@@ -3,25 +3,24 @@
<v-container fluid class="hero-section">
<v-row justify="center">
<v-col cols="12" md="8" class="text-center">
<h1 class="display-1 px-8">You're Being Tracked by ALPRs!</h1>
<ALPRCounter class="mt-4" />
<p class="subtitle-1 px-8 mt-6 mb-12 bigger">
Automated License Plate Readers (ALPRs) are monitoring your every move. Learn more about how they work and how you can protect your privacy.
</p>
<v-btn color="rgb(18, 151, 195)" large @click="goToMap({ withCurrentLocation: true })">
Find Nearby ALPRs
<v-icon end>mdi-map</v-icon>
</v-btn>
<h1 class="display-1 px-8">You're Being Tracked</h1>
<ALPRCounter class="my-6" />
<!-- Featured On Section -->
<v-container class="featured-on-section mt-10">
<h4 class="mb-8" style="opacity: 0.8">Featured On</h4>
<v-container class="featured-on-section my-8">
<h4 class="mb-4" style="opacity: 0.8">Featured On</h4>
<v-row justify="center" align-items="center">
<v-card flat v-for="site in featuredOn" class="mx-4" :width="site.wide ? 200 : 100" height="50" :href="site.url" target="_blank" style="background: rgba(0,0,0,0)">
<v-card flat v-for="site in featuredOn" class="mx-4" :width="site.wide ? 200 : 100" height="50" style="background: rgba(0,0,0,0)">
<v-img contain :src="site.logo" :alt="site.name" class="featured-logo" style="display: flex; align-items: center; height: 100%;" />
</v-card>
</v-row>
</v-container>
<v-btn size="large" color="rgb(18, 151, 195)" large @click="goToMap({ withCurrentLocation: true })">
Explore the Map
<v-icon end>mdi-map</v-icon>
</v-btn>
</v-col>
</v-row>
</v-container>
@@ -30,14 +29,14 @@
<v-container class="py-10 text-center info-section">
<h2 class="mb-4">What is an ALPR</h2>
<p class="text-left">
<p class="text-left px-6">
Automated License Plate Readers (ALPRs) are cameras that capture images of all passing license plates, storing details like the car's location, date, and time. These cameras collect data on millions of vehiclesregardless of whether the driver is suspected of a crime. While these systems can be useful for tracking stolen cars or wanted individuals, they are mostly used to track the movements of innocent people.
</p>
<v-divider class="my-8" />
<h2 class="display-2 mb-4">The Dangers of ALPRs</h2>
<p class="subtitle-1 px-8">
<p class="px-6">
ALPRs are a threat to your privacy and civil liberties. They can be used to track your movements and profile you, and even stalk you. Learn more about the dangers of ALPRs and how you can protect yourself.
</p>
@@ -49,7 +48,7 @@
Privacy Violations
</v-card-title>
<v-card-text>
ALPRs track your movements and store your data for long periods of time, creating a detailed record of your location history. They surveil mostly innocent people while claiming to target criminals.
ALPRs track your movements and store your data for long periods of time, creating a detailed record of your location history.
</v-card-text>
</v-card>
</v-col>
@@ -100,7 +99,7 @@
background: url('/flock-camera.jpeg') no-repeat center center;
background-size: cover;
color: white;
padding: 100px 0 50px 0 !important;
padding: 60px 0 50px 0 !important;
position: relative;
}
@@ -111,7 +110,7 @@
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
background: rgba(0, 0, 0, 0.55);
z-index: 1;
}
@@ -121,7 +120,7 @@
}
.map-section {
background: url('/deflock-screenshot.webp') no-repeat right center;
background: url('/deflock-screenshot.webp') no-repeat center center;
background-size: cover;
color: white;
padding: 100px 0;

View File

@@ -112,7 +112,9 @@ function goToUserLocation() {
setCurrentLocation()
.then((cl) => {
center.value = cl;
zoom.value = DEFAULT_ZOOM;
setTimeout(() => {
zoom.value = DEFAULT_ZOOM;
}, 10);
})
.catch(error => {
console.debug('Error getting user location.', error);

View File

@@ -1,162 +0,0 @@
<template>
<!-- Hero Section -->
<v-container fluid class="hero-section">
<v-row justify="center">
<v-col cols="12" md="8" class="text-center">
<h1 class="display-1 px-8">You're Being Tracked by an ALPR!</h1>
<ALPRCounter class="mt-4" />
<p class="subtitle-1 px-8 mt-6 mb-12 bigger">
Automated License Plate Readers (ALPRs) are monitoring your every move. Learn more about how they work and how you can protect your privacy.
</p>
<v-btn color="rgb(18, 151, 195)" large @click="goToMap({ withCurrentLocation: true })">
Find the Nearest ALPR
<v-icon end>mdi-map</v-icon>
</v-btn>
</v-col>
</v-row>
</v-container>
<!-- Dangers Section -->
<v-container class="py-10 text-center info-section">
<h2 class="display-2 mb-4">The Dangers of ALPRs</h2>
<p class="subtitle-1 px-8">
ALPRs are a threat to your privacy and civil liberties. They can be used to track your movements, profile you, and even stalk you. Learn more about the dangers of ALPRs and how you can protect yourself.
</p>
<v-row class="align-center mt-4">
<v-col cols="12" md="4" class="text-center">
<v-card>
<v-card-title class="headline">
<v-icon x-large class="mr-2">mdi-shield-alert</v-icon>
Privacy Violations
</v-card-title>
<v-card-text>
ALPRs track your movements and store your data for long periods of time, creating a detailed record of your location history. They surveil mostly innocent people while claiming to target criminals.
</v-card-text>
</v-card>
</v-col>
<v-col cols="12" md="4" class="text-center">
<v-card>
<v-card-title class="headline">
<v-icon x-large class="mr-2">mdi-alert-circle</v-icon>
Risk of Misuse
</v-card-title>
<v-card-text>
Data from ALPRs has led to <a target="_blank" href="https://www.newsobserver.com/news/state/north-carolina/article287381160.html">wrongful arrests</a>, profiling, and <a target="_blank" href="https://www.kwch.com/2022/10/31/kechi-police-lieutenant-arrested-using-police-technology-stalk-wife/">stalking ex-partners</a> by police officers.
</v-card-text>
</v-card>
</v-col>
<v-col cols="12" md="4" class="text-center">
<v-card>
<v-card-title class="headline">
<v-icon x-large class="mr-2">mdi-handcuffs</v-icon>
Limited Benefits
</v-card-title>
<v-card-text>
There's no substantial evidence that ALPRs effectively prevent crime, despite <a target="_blank" href="https://www.404media.co/researcher-who-oversaw-flock-surveillance-study-now-has-concerns-about-it/">Flock's unethical attempts</a> to prove otherwise.
</v-card-text>
</v-card>
</v-col>
</v-row>
<v-btn class="mt-8" color="rgb(18, 151, 195)" large to="/dangers">
See All Dangers
<v-icon end>mdi-shield-alert</v-icon>
</v-btn>
</v-container>
<!-- Map Section -->
<v-container class="map-section py-10 text-center">
<h2 class="display-2 mb-4">Explore ALPR Locations Near You</h2>
<v-btn color="white" large @click="goToMap">
View the Map
<v-icon end>mdi-map</v-icon>
</v-btn>
</v-container>
</template>
<style>
.hero-section {
background: url('/flock-camera.jpeg') no-repeat center center;
background-size: cover;
color: white;
padding: 100px 0 !important;
position: relative;
}
.hero-section::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
z-index: 1;
}
.hero-section > * {
position: relative;
z-index: 2;
}
.info-section {
background: #f5f5f5;
}
.map-section {
background: url('/deflock-screenshot.webp') no-repeat center center;
background-size: cover;
color: white;
padding: 100px 0;
position: relative;
}
.map-section::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.4);
z-index: 1;
}
.map-section > * {
position: relative;
z-index: 2;
}
.bigger {
font-size: 1.1rem;
}
</style>
<script setup lang="ts">
import { useRouter } from 'vue-router';
import ALPRCounter from '@/components/ALPRCounter.vue';
import { useGlobalStore } from '@/stores/global';
const router = useRouter();
const { setCurrentLocation } = useGlobalStore();
interface GoToMapOptions {
withCurrentLocation?: boolean;
}
async function goToMap(options: GoToMapOptions = {}) {
if (options.withCurrentLocation) {
setCurrentLocation()
.then((currentLocation) => {
const [lat, lon] = currentLocation;
router.push({ path: '/map', hash: `#map=16/${lat.toFixed(6)}/${lon.toFixed(6)}` });
})
.catch(() => {
router.push({ path: '/map' });
});
} else {
router.push({ path: '/map' });
}
}
</script>

View File

@@ -1,6 +1,6 @@
<template>
<v-container>
<h1 class="text-center">Report an ALPR</h1>
<h1 class="text-center mt-4">Report a New ALPR</h1>
<p>
If you've spotted an ALPR in your area, you can help us track it by reporting it to OpenStreetMap, where we source our information. Here's how you can do it:
@@ -9,6 +9,7 @@
<v-stepper-vertical color="rgb(18, 151, 195)" v-model="step" flat non-linear class="my-8" edit-icon="mdi-home">
<template v-slot:default="{ step }: { step: any }">
<v-stepper-vertical-item
class="transparent"
:complete="step > 1"
title="Create an OpenStreetMap Account"
value="1"
@@ -20,6 +21,7 @@
</v-stepper-vertical-item>
<v-stepper-vertical-item
class="transparent"
:complete="step > 2"
title="Find the ALPR's Location"
value="2"
@@ -31,6 +33,7 @@
</v-stepper-vertical-item>
<v-stepper-vertical-item
class="transparent"
:complete="step > 3"
title="Add the ALPR to OpenStreetMap"
value="3"
@@ -39,7 +42,7 @@
<p>
Once you've found the location of the ALPR, click the <strong>Edit</strong> button in the top left corner of the page. This will open the OpenStreetMap editor, where you can add the ALPR to the map.
</p>
<v-img max-width="450" src="/edit-map.png" class="my-8 mx-auto" />
<v-img max-width="450" src="/edit-map.png" class="my-8" />
<p class="mt-16 mb-8">
To add the ALPR, click the <strong>Point</strong> button in the top left corner of the editor, then click on the location of the ALPR on the map. In the popup that appears, paste one of the following sets of tags based on the brand of the ALPR:
</p>
@@ -53,10 +56,11 @@
<p class="mt-8">
After copying the tags, paste them into the <strong>Tags</strong> field in the popup.
</p>
<v-img max-width="450" class="my-8 mx-auto" src="/paste-tags.png" />
<v-img max-width="450" class="my-8" src="/paste-tags.png" />
</v-stepper-vertical-item>
<v-stepper-vertical-item
class="transparent"
:complete="step > 4"
title="Adjust the Direction"
value="4"
@@ -69,6 +73,7 @@
</v-stepper-vertical-item>
<v-stepper-vertical-item
class="transparent"
:complete="step > 5"
title="Submit Your Changes"
value="5"
@@ -90,6 +95,7 @@
</v-stepper-vertical-item>
<v-stepper-vertical-item
class="transparent"
:complete="step > 6"
title="Hang a Sign"
value="6"
@@ -98,9 +104,28 @@
<p>
Download our <a href="/deflock-poster.pdf" target="_blank">ALPR sign</a> and hang it near the ALPR to help raise awareness about the device. Be sure to follow all local laws and regulations when hanging signs.
</p>
<template v-slot:next>
<v-btn
color="primary"
disabled
>
Next
</v-btn>
</template>
</v-stepper-vertical-item>
</template>
</v-stepper-vertical>
<h2 class="text-center mt-16">Edit an Existing ALPR</h2>
<p>
If you find an ALPR that's missing information and would like to update it, you can follow the same steps as above. Each ALPR on DeFlock has a Node ID that you can use to find it on OpenStreetMap.
</p>
<p class="mb-16">
Simply click on the ALPR with missing information, and find the Node ID (e.g. <code>node/1237489334</code>) at the bottom of the popup. In the OSM editor search field, paste the <i>numerical portion</i> of the Node ID to find the ALPR and make your changes.
</p>
</v-container>
<Footer />
</template>
@@ -116,4 +141,8 @@ const step = ref(1);
<style scoped>
@import url('@/assets/typography.css');
.transparent {
background-color: transparent !important;
}
</style>

View File

@@ -12,5 +12,9 @@ export default defineConfig({
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
},
server: {
host: '0.0.0.0',
port: 5173,
}
})