Set initial camera based on location in compliance

This commit is contained in:
Eli Ribble 2026-04-10 14:20:04 +00:00
parent 97acdb0e2c
commit c48aebcb0b
No known key found for this signature in database
6 changed files with 83 additions and 29 deletions

View file

@ -177,7 +177,8 @@ interface Emits {
// Props // Props
interface Props { interface Props {
modelValue: Camera | null; initialCamera?: Camera;
modelValue: Camera;
markers?: Marker[]; markers?: Marker[];
} }
@ -225,12 +226,7 @@ function deactivateMap() {
function initializeMap() { function initializeMap() {
if (!mapContainer.value) return; if (!mapContainer.value) return;
let bounds = boundsDefault();
if (props.markers.length > 0) {
bounds = boundsMarkers(props.markers);
}
const _map = new maplibregl.Map({ const _map = new maplibregl.Map({
bounds: bounds,
container: mapContainer.value, container: mapContainer.value,
style: "https://tiles.stadiamaps.com/styles/alidade_smooth.json", style: "https://tiles.stadiamaps.com/styles/alidade_smooth.json",
// Disable interactions by default // Disable interactions by default
@ -239,6 +235,30 @@ function initializeMap() {
scrollZoom: false, scrollZoom: false,
touchZoomRotate: false, touchZoomRotate: false,
}); });
if (props.markers.length > 0) {
_map.fitBounds(boundsMarkers(props.markers));
} else if (props.initialCamera) {
_map.jumpTo({
center: [
props.initialCamera.location.longitude,
props.initialCamera.location.latitude,
],
zoom: props.initialCamera.zoom,
});
} else if (
props.modelValue.location.latitude != 0 ||
props.modelValue.location.longitude != 0
) {
_map.jumpTo({
center: [
props.modelValue.location.longitude,
props.modelValue.location.latitude,
],
zoom: props.modelValue.zoom,
});
} else {
_map.fitBounds(boundsDefault());
}
_map.addControl(new maplibregl.NavigationControl(), "top-left"); _map.addControl(new maplibregl.NavigationControl(), "top-left");
map.value = _map; map.value = _map;
_map.on("click", (e: maplibregl.MapLayerMouseEvent) => { _map.on("click", (e: maplibregl.MapLayerMouseEvent) => {

View file

@ -50,10 +50,11 @@
<label class="form-label fw-semibold">Location Preview</label> <label class="form-label fw-semibold">Location Preview</label>
<div class="map-container"> <div class="map-container">
<MapLocator <MapLocator
v-model="currentCamera" :initialCamera="initialCamera"
:markers="markers" :markers="markers"
@click="doMapClick" @click="doMapClick"
@marker-drag-end="doMapMarkerDragEnd" @marker-drag-end="doMapMarkerDragEnd"
v-model="currentCamera"
/> />
</div> </div>
</div> </div>
@ -64,17 +65,18 @@ import AddressSuggestion from "@/components/AddressSuggestion.vue";
import MapLocator from "@/components/MapLocator.vue"; import MapLocator from "@/components/MapLocator.vue";
import type { Address, Geocode, GeocodeSuggestion, Location } from "@/type/api"; import type { Address, Geocode, GeocodeSuggestion, Location } from "@/type/api";
import { useGeocodeStore } from "@/store/geocode"; import { useGeocodeStore } from "@/store/geocode";
import type { Camera, Locator } from "@/type/map"; import { Camera, Locator } from "@/type/map";
import type { Marker } from "@/types"; import type { Marker } from "@/types";
interface Emits { interface Emits {
(e: "update:modelValue", value: Locator): void; (e: "update:modelValue", value: Locator): void;
} }
interface Props { interface Props {
initialCamera?: Camera;
modelValue: Locator; modelValue: Locator;
} }
const address = ref<string>(""); const address = ref<string>("");
const currentCamera = ref<Camera | null>(null); const currentCamera = ref<Camera>(new Camera());
const emit = defineEmits<Emits>(); const emit = defineEmits<Emits>();
const geocode = useGeocodeStore(); const geocode = useGeocodeStore();
const markers = computed((): Marker[] => { const markers = computed((): Marker[] => {

View file

@ -10,7 +10,10 @@
Please enter the address so we can match your response with our records. Please enter the address so we can match your response with our records.
</p> </p>
<AddressAndMapLocator v-model="modelValue.locator" /> <AddressAndMapLocator
:initialCamera="initialCamera"
v-model="modelValue.locator"
/>
<div class="d-flex gap-2 mt-4"> <div class="d-flex gap-2 mt-4">
<RouterLink class="btn btn-outline-secondary" to="../compliance"> <RouterLink class="btn btn-outline-secondary" to="../compliance">
@ -24,7 +27,7 @@
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from "vue"; import { computed, ref } from "vue";
import { router } from "@/rmo/router"; import { router } from "@/rmo/router";
import type { District } from "@/type/api"; import type { District } from "@/type/api";
@ -32,7 +35,7 @@ import HeaderCompliance from "@/rmo/components/HeaderCompliance.vue";
import ProgressBarCompliance from "@/rmo/components/ProgressBarCompliance.vue"; import ProgressBarCompliance from "@/rmo/components/ProgressBarCompliance.vue";
import AddressAndMapLocator from "@/rmo/components/AddressAndMapLocator.vue"; import AddressAndMapLocator from "@/rmo/components/AddressAndMapLocator.vue";
import { Compliance } from "@/rmo/view/Compliance.vue"; import { Compliance } from "@/rmo/view/Compliance.vue";
import { Locator } from "@/type/map"; import { Camera, Locator } from "@/type/map";
interface Emits { interface Emits {
(e: "doLocator"): void; (e: "doLocator"): void;
@ -45,6 +48,15 @@ interface Props {
const emit = defineEmits<Emits>(); const emit = defineEmits<Emits>();
const error = ref<string>(""); const error = ref<string>("");
const props = defineProps<Props>(); const props = defineProps<Props>();
const initialCamera = computed((): Camera | undefined => {
if (props.modelValue.location) {
return {
location: props.modelValue.location,
zoom: 15,
};
}
return undefined;
});
function doContinue() { function doContinue() {
emit("update:modelValue", props.modelValue); emit("update:modelValue", props.modelValue);
emit("doLocator"); emit("doLocator");

View file

@ -43,7 +43,8 @@ import { useStoreDistrict } from "@/rmo/store/district";
import { useStoreLocal } from "@/store/local"; import { useStoreLocal } from "@/store/local";
import { useStoreLocation } from "@/store/location"; import { useStoreLocation } from "@/store/location";
import Intro from "@/rmo/content/compliance/Intro.vue"; import Intro from "@/rmo/content/compliance/Intro.vue";
import { type District, PermissionAccess, type PublicReport } from "@/type/api"; import type { District, Location, PublicReport } from "@/type/api";
import { PermissionAccess } from "@/type/api";
import { Locator } from "@/type/map"; import { Locator } from "@/type/map";
import { type Contact } from "@/rmo/content/compliance/Contact.vue"; import { type Contact } from "@/rmo/content/compliance/Contact.vue";
import { type Permission } from "@/rmo/content/compliance/Permission.vue"; import { type Permission } from "@/rmo/content/compliance/Permission.vue";
@ -51,6 +52,7 @@ import { type Permission } from "@/rmo/content/compliance/Permission.vue";
export interface Compliance { export interface Compliance {
comments: string; comments: string;
contact: Contact; contact: Contact;
location: Location;
locator: Locator; locator: Locator;
images: Image[]; images: Image[];
permission: Permission; permission: Permission;
@ -70,6 +72,10 @@ const compliance = ref<Compliance>({
email: "", email: "",
}, },
images: [], images: [],
location: {
latitude: 0,
longitude: 0,
},
locator: { locator: {
address: { address: {
country: "", country: "",
@ -134,6 +140,7 @@ onMounted(() => {
storeLocation storeLocation
.get() .get()
.then((loc: GeolocationPosition) => { .then((loc: GeolocationPosition) => {
compliance.value.location = loc.coords;
createReport(session_id, loc); createReport(session_id, loc);
}) })
.catch((e) => { .catch((e) => {

View file

@ -4,16 +4,18 @@ export enum PermissionAccess {
UNSELECTED = "unselected", UNSELECTED = "unselected",
WITH_OWNER = "with-owner", WITH_OWNER = "with-owner",
} }
export interface Address { export class Address {
country: string; constructor(
gid: string; public country: string,
locality: string; public gid: string,
number: string; public locality: string,
postal_code: string; public number: string,
raw: string; public postal_code: string,
region: string; public raw: string,
street: string; public region: string,
unit: string; public street: string,
public unit: string,
) {}
} }
export interface Bounds { export interface Bounds {
min: Location; min: Location;
@ -32,10 +34,15 @@ export interface District {
url_logo: string; url_logo: string;
url_website: string; url_website: string;
} }
export interface Location { export class Location {
accuracy?: number; accuracy?: number;
latitude: number; latitude: number;
longitude: number; longitude: number;
constructor(latitude: number = 0, longitude: number = 0, accuracy?: number) {
this.accuracy = accuracy;
this.latitude = latitude;
this.longitude = longitude;
}
} }
export interface GeocodeSuggestion { export interface GeocodeSuggestion {
detail: string; detail: string;

View file

@ -1,13 +1,19 @@
import maplibregl from "maplibre-gl"; import maplibregl from "maplibre-gl";
import type { Address, Location } from "@/type/api"; import { Address, Location } from "@/type/api";
export interface Camera { export class Camera {
location: Location; location: Location;
zoom: number; zoom: number;
constructor(location: Location = new Location(), zoom: number = 0) {
this.location = location;
this.zoom = zoom;
}
} }
export interface Locator { export class Locator {
address: Address; constructor(
location: Location; public address: Address,
public location: Location,
) {}
} }
export type MoveEndEventInternal = maplibregl.MapLibreEvent< export type MoveEndEventInternal = maplibregl.MapLibreEvent<
| maplibregl.MapMouseEvent | maplibregl.MapMouseEvent