From 946f8dd342ec315b7b0574cb08c034c83c248e08 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 23 Mar 2026 22:02:42 +0000 Subject: [PATCH] Add initial region loading for supported systems Expose an initialRegion field on the System GraphQL type so the app can display the correct campus area on first load instead of a zoomed-out default map view. Chapman University is configured with coordinates centered on campus. https://claude.ai/code/session_0162emnCi1KxW5FkTNn2ZrvH --- schema.graphqls | 8 +++++ src/entities/InterchangeSystem.ts | 10 +++++++ src/entities/SharedEntities.ts | 7 +++++ src/index.ts | 6 ++++ src/resolvers/SystemResolvers.ts | 8 +++++ .../__tests__/SystemResolverTests.test.ts | 29 +++++++++++++++++++ testHelpers/apolloTestServerHelpers.ts | 6 ++++ 7 files changed, 74 insertions(+) diff --git a/schema.graphqls b/schema.graphqls index 9c2b390..d114e1a 100644 --- a/schema.graphqls +++ b/schema.graphqls @@ -6,6 +6,7 @@ scalar DateTime type System { id: ID! name: String! + initialRegion: Region routes: [Route!] route(id: ID): Route stops: [Stop!] @@ -16,6 +17,13 @@ type System { parkingSystem: ParkingSystem } +type Region { + latitude: Float! + longitude: Float! + latitudeDelta: Float! + longitudeDelta: Float! +} + type ParkingSystem { systemId: ID! parkingStructures: [ParkingStructure!] diff --git a/src/entities/InterchangeSystem.ts b/src/entities/InterchangeSystem.ts index 3bd7e3f..d183f23 100644 --- a/src/entities/InterchangeSystem.ts +++ b/src/entities/InterchangeSystem.ts @@ -19,6 +19,7 @@ import { InMemoryExternalSourceETARepository } from "../repositories/shuttle/eta import { ETAGetterRepository } from "../repositories/shuttle/eta/ETAGetterRepository"; import { InMemorySelfUpdatingETARepository } from "../repositories/shuttle/eta/InMemorySelfUpdatingETARepository"; import { BaseInMemoryETARepository } from "../repositories/shuttle/eta/BaseInMemoryETARepository"; +import { IRegion } from "./SharedEntities"; export interface InterchangeSystemBuilderArguments { name: string; @@ -49,6 +50,12 @@ export interface InterchangeSystemBuilderArguments { * at a stop, in latitude/longitude degrees. */ shuttleStopArrivalDegreeDelta: number; + + /** + * The initial map region to display when the app first loads + * this system. Represents a center coordinate and span. + */ + initialRegion?: IRegion; } export class InterchangeSystem { @@ -62,6 +69,7 @@ export class InterchangeSystem { public notificationRepository: NotificationRepository, public parkingTimedDataLoader: TimedApiBasedRepositoryLoader | null, public parkingRepository: ParkingGetterSetterRepository | null, + public initialRegion: IRegion | null, ) { } @@ -96,6 +104,7 @@ export class InterchangeSystem { notificationRepository, timedParkingLoader, parkingRepository, + args.initialRegion ?? null, ); } @@ -205,6 +214,7 @@ export class InterchangeSystem { notificationRepository, timedParkingLoader, parkingRepository, + args.initialRegion ?? null, ); } diff --git a/src/entities/SharedEntities.ts b/src/entities/SharedEntities.ts index 67aa03e..e24f31a 100644 --- a/src/entities/SharedEntities.ts +++ b/src/entities/SharedEntities.ts @@ -11,3 +11,10 @@ export interface ICoordinates { longitude: number; } +export interface IRegion { + latitude: number; + longitude: number; + latitudeDelta: number; + longitudeDelta: number; +} + diff --git a/src/index.ts b/src/index.ts index 7340b04..24bfeb3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -25,6 +25,12 @@ const supportedSystems: InterchangeSystemBuilderArguments[] = [ name: "Chapman University", useSelfUpdatingEtas: true, shuttleStopArrivalDegreeDelta: 0.001, + initialRegion: { + latitude: 33.7937, + longitude: -117.8531, + latitudeDelta: 0.01, + longitudeDelta: 0.01, + }, } ] diff --git a/src/resolvers/SystemResolvers.ts b/src/resolvers/SystemResolvers.ts index 27d46c9..538dea0 100644 --- a/src/resolvers/SystemResolvers.ts +++ b/src/resolvers/SystemResolvers.ts @@ -83,6 +83,14 @@ export const SystemResolvers: Resolvers = { const shuttles = await system.shuttleRepository.getShuttles(); return shuttles.slice().sort((a, b) => a.name.localeCompare(b.name)); }, + initialRegion: async (parent, _args, contextValue, _info) => { + const system = contextValue.findSystemById(parent.id); + if (!system) { + return null; + } + + return system.initialRegion; + }, parkingSystem: async (parent, _args, contextValue, _info) => { const system = contextValue.findSystemById(parent.id); if (!system) { diff --git a/src/resolvers/__tests__/SystemResolverTests.test.ts b/src/resolvers/__tests__/SystemResolverTests.test.ts index 822ae4d..a345469 100644 --- a/src/resolvers/__tests__/SystemResolverTests.test.ts +++ b/src/resolvers/__tests__/SystemResolverTests.test.ts @@ -35,6 +35,35 @@ describe("SystemResolvers", () => { }); } + describe("initialRegion", () => { + const query = ` + query GetSystemInitialRegion($systemId: ID!) { + system(id: $systemId) { + initialRegion { + latitude + longitude + latitudeDelta + longitudeDelta + } + } + } + `; + + it("returns the initial region for the system", async () => { + const response = await getResponseFromQueryNeedingSystemId(query); + + assert(response.body.kind === "single"); + expect(response.body.singleResult.errors).toBeUndefined(); + const initialRegion = (response.body.singleResult.data as any).system.initialRegion; + expect(initialRegion).toEqual({ + latitude: 33.7937, + longitude: -117.8531, + latitudeDelta: 0.01, + longitudeDelta: 0.01, + }); + }); + }); + describe("routes", () => { const query = ` query GetSystemRoutes($systemId: ID!) { diff --git a/testHelpers/apolloTestServerHelpers.ts b/testHelpers/apolloTestServerHelpers.ts index 6f32883..e8d721c 100644 --- a/testHelpers/apolloTestServerHelpers.ts +++ b/testHelpers/apolloTestServerHelpers.ts @@ -26,6 +26,12 @@ const systemInfoForTesting: InterchangeSystemBuilderArguments = { parkingSystemId: ChapmanApiBasedParkingRepositoryLoader.id, useSelfUpdatingEtas: false, shuttleStopArrivalDegreeDelta: 0.001, + initialRegion: { + latitude: 33.7937, + longitude: -117.8531, + latitudeDelta: 0.01, + longitudeDelta: 0.01, + }, }; export function buildSystemForTesting() {