Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 53 additions & 1 deletion src/parks/enchantedparks/__tests__/enchantedparks.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {describe, test, expect} from 'vitest';
import {parseTribeEvents, type TribeEventsResponse} from '../enchantedparks.js';
import {parseTribeEvents, type TribeEventsResponse, EnchantedParks} from '../enchantedparks.js';
import {parseICalFeed} from '../enchantedparks.js';
import {parseAttractionsPage} from '../enchantedparks.js';

Expand Down Expand Up @@ -262,3 +262,55 @@ describe('parseAttractionsPage', () => {
expect(out).toEqual([{slug: 'renegade', name: 'Renegade'}]);
});
});

describe('attraction location lookup', () => {
// Expose protected `lookupAttractionLocation` for direct testing without
// requiring a full destination lifecycle.
class Probe extends EnchantedParks {
public withLocations(
m: Record<string, {latitude: number; longitude: number}>,
): this {
this.attractionLocations = m;
return this;
}
public lookup(name: string) {
return this.lookupAttractionLocation(name);
}
}

const sample = {
"Snoopy's Junction": {latitude: 39.172367, longitude: -94.488782},
'Timber Wolf': {latitude: 39.173334, longitude: -94.488856},
'TIMBERTOWN RAILWAY': {latitude: 43.342000, longitude: -86.275000},
};

test('matches when WP source uses curly apostrophe and snapshot uses straight', () => {
const p = new Probe({}).withLocations(sample);
// Wiki snapshot has "Snoopy's Junction" (straight ').
// WP source emits "Snoopy’s Junction" (curly ’).
expect(p.lookup('Snoopy’s Junction')).toEqual({
latitude: 39.172367, longitude: -94.488782,
});
});

test('matches case-insensitively', () => {
const p = new Probe({}).withLocations(sample);
expect(p.lookup('timber wolf')).toEqual({
latitude: 39.173334, longitude: -94.488856,
});
// Lookup name uppercase, snapshot key uppercase — still matches.
expect(p.lookup('Timbertown Railway')).toEqual({
latitude: 43.342000, longitude: -86.275000,
});
});

test('returns undefined when the name is not in the snapshot', () => {
const p = new Probe({}).withLocations(sample);
expect(p.lookup('Definitely Not A Real Ride')).toBeUndefined();
});

test('returns undefined when no snapshot is configured', () => {
const p = new Probe({});
expect(p.lookup('Timber Wolf')).toBeUndefined();
});
});
55 changes: 51 additions & 4 deletions src/parks/enchantedparks/enchantedparks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,47 @@ class EnchantedParks extends Destination {
waterPark?: ParkConfig;
/** Destination-level geographic location (lat/lng) */
destinationLocation?: {latitude: number; longitude: number};

/**
* Optional snapshot of per-attraction coordinates, keyed by attraction
* name. The EP WordPress source does not expose ride-level lat/lng, so
* without this every collector tick proposes deleting the existing
* location data on each attraction. Subclasses load a static JSON
* snapshot from `locations/<slug>.json` taken when the park was migrated.
* Attractions added by EP later will land without coordinates and need
* the moderation queue.
*/
protected attractionLocations?: Record<string, {latitude: number; longitude: number}>;

/**
* Lookup table for attractionLocations, keyed by normalized name. Lazily
* built on first use so subclasses can assign `attractionLocations` in
* the constructor without ordering concerns.
*/
private normalizedLocations?: Map<string, {latitude: number; longitude: number}>;

/**
* Normalize ride names for cross-source matching. The WP source uses
* curly apostrophes (’ U+2019) while the wiki snapshot stores straight
* ones — without folding both to the same form we lose ~20% of matches.
*/
private normalizeName(name: string): string {
return name.replace(/[‘’]/g, "'").toLowerCase();
}

protected lookupAttractionLocation(
rideName: string,
): {latitude: number; longitude: number} | undefined {
if (!this.attractionLocations) return undefined;
if (!this.normalizedLocations) {
Comment on lines +204 to +208
this.normalizedLocations = new Map(
Object.entries(this.attractionLocations).map(
([k, v]) => [this.normalizeName(k), v] as const,
),
);
}
return this.normalizedLocations.get(this.normalizeName(rideName));
}
/** IANA timezone for the destination */
@config timezone: string = 'America/Chicago';

Expand Down Expand Up @@ -345,15 +386,18 @@ class EnchantedParks extends Destination {
}
parks.push(wpEntity);
for (const r of wpRides) {
attractions.push({
const entity: Entity = {
id: `enchantedparks_attraction_${this.waterPark.code}_${r.slug}`,
name: r.name,
entityType: 'ATTRACTION',
parentId: this.waterPark.id,
parkId: this.waterPark.id,
destinationId: this.destinationId,
timezone: this.timezone,
} as Entity);
} as Entity;
const loc = this.lookupAttractionLocation(r.name);
if (loc) (entity as any).location = loc;
attractions.push(entity);
}
}

Expand All @@ -376,15 +420,18 @@ class EnchantedParks extends Destination {
// ones. Skip slugs already claimed by the waterpark page so they don't
// double-emit.
if (waterParkSlugs.has(r.slug)) continue;
attractions.push({
const entity: Entity = {
id: `enchantedparks_attraction_${this.themePark.code}_${r.slug}`,
name: r.name,
entityType: 'ATTRACTION',
parentId: this.themePark.id,
parkId: this.themePark.id,
destinationId: this.destinationId,
timezone: this.timezone,
} as Entity);
} as Entity;
const loc = this.lookupAttractionLocation(r.name);
if (loc) (entity as any).location = loc;
attractions.push(entity);
}
}

Expand Down
218 changes: 218 additions & 0 deletions src/parks/enchantedparks/locations/michigansadventure.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
{
"Winky the Whale": {
"latitude": 43.342773,
"longitude": -86.280718
},
"Thunderbolt": {
"latitude": 43.341675,
"longitude": -86.277131
},
"Sea Dragon": {
"latitude": 43.342564,
"longitude": -86.27798
},
"Beagle Scout Lookout": {
"latitude": 43.343597,
"longitude": -86.280306
},
"Shivering Timbers": {
"latitude": 43.342224,
"longitude": -86.277034
},
"Drummer Boy": {
"latitude": 43.342201,
"longitude": -86.279457
},
"Airplanes": {
"latitude": 43.342281,
"longitude": -86.279455
},
"Carousel": {
"latitude": 43.343565,
"longitude": -86.279765
},
"Corkscrew": {
"latitude": 43.34301,
"longitude": -86.279631
},
"Speed Splashers": {
"latitude": 43.342513,
"longitude": -86.280644
},
"Zach's Zoomer": {
"latitude": 43.342938,
"longitude": -86.280651
},
"Ripcord": {
"latitude": 43.345541,
"longitude": -86.278429
},
"Trabant": {
"latitude": 43.341882,
"longitude": -86.277996
},
"Mad Mouse": {
"latitude": 43.341757,
"longitude": -86.279171
},
"Thunderhawk": {
"latitude": 43.345384,
"longitude": -86.27728
},
"Tilt-A-Whirl": {
"latitude": 43.341822,
"longitude": -86.27771
},
"Timbertown Railway": {
"latitude": 43.342706,
"longitude": -86.276606
},
"Pigpen's Mud Buggies": {
"latitude": 43.343341,
"longitude": -86.280302
},
"Motorcycles": {
"latitude": 43.342732,
"longitude": -86.280518
},
"Scrambler": {
"latitude": 43.341889,
"longitude": -86.277053
},
"Elephants": {
"latitude": 43.342436,
"longitude": -86.279371
},
"Giant Gondola Wheel": {
"latitude": 43.342019,
"longitude": -86.279445
},
"Woodstock Express": {
"latitude": 43.343539,
"longitude": -86.2804
},
"Dodgem": {
"latitude": 43.342396,
"longitude": -86.279114
},
"Frog Hopper": {
"latitude": 43.341773,
"longitude": -86.27933
},
"Wolverine Wildcat": {
"latitude": 43.342652,
"longitude": -86.277388
},
"Kiddie Cars": {
"latitude": 43.3428,
"longitude": -86.280488
},
"Lakeside Gliders": {
"latitude": 43.342605,
"longitude": -86.278948
},
"Flying Trapeze": {
"latitude": 43.341704,
"longitude": -86.277788
},
"Camp Bus": {
"latitude": 43.343789,
"longitude": -86.280262
},
"Boogie Beach": {
"latitude": 43.345329,
"longitude": -86.281135
},
"HydroBlaster": {
"latitude": 43.345608,
"longitude": -86.278838
},
"Funnel of Fear": {
"latitude": 43.345487,
"longitude": -86.279475
},
"PEANUTS™ Trailblazers": {
"latitude": 43.343129,
"longitude": -86.280398
},
"Timbertown Railway Station #2": {
"latitude": 43.34488,
"longitude": -86.27638
},
"Ridge Rider & Wild Slide": {
"latitude": 43.346557,
"longitude": -86.278725
},
"Slidewinders": {
"latitude": 43.345435,
"longitude": -86.280595
},
"Tidal Wave": {
"latitude": 43.345972,
"longitude": -86.279747
},
"Adventure Falls": {
"latitude": 43.345524,
"longitude": -86.277304
},
"Grand Rapids": {
"latitude": 43.34553,
"longitude": -86.276354
},
"Logger's Run": {
"latitude": 43.342433,
"longitude": -86.277639
},
"Swan Boats": {
"latitude": 43.345437,
"longitude": -86.278775
},
"Commotion Ocean": {
"latitude": 43.344728,
"longitude": -86.280463
},
"Half-Pint Paradise": {
"latitude": 43.345403,
"longitude": -86.280094
},
"Paradise Plunge & Tropical Twist": {
"latitude": 43.344842,
"longitude": -86.2799
},
"Beach Party": {
"latitude": 43.344748,
"longitude": -86.280298
},
"Cyclone Zone": {
"latitude": 43.346623,
"longitude": -86.278735
},
"Lazy River": {
"latitude": 43.346108,
"longitude": -86.27945
},
"Mammoth River": {
"latitude": 43.346014,
"longitude": -86.280555
},
"Mine Shaft": {
"latitude": 43.345956,
"longitude": -86.28073
},
"Snake Pit": {
"latitude": 43.344853,
"longitude": -86.281038
},
"Rocky Point Mini-Golf": {
"latitude": 43.343669,
"longitude": -86.279123
},
"Lagoon": {
"latitude": 43.34521,
"longitude": -86.280811
},
"Beagle Scout Acres": {
"latitude": 43.343195,
"longitude": -86.279981
}
}
Loading
Loading