Data Loading - Places and Subplaces
This guide explains how to load and manage places and subplaces data with the MapVX Web SDK.
Available Data Types
1. Available Places
Main places of an institution that are available for navigation.
2. Subplaces
Specific places within a parent place (building, floor, zone, etc.).
Data Loading Methods
Load Available Places
import { initializeSDK } from "@mapvx/web-js"
async function loadAvailablePlaces() {
const sdk = initializeSDK("your-api-key", { lang: "en" })
try {
// Load available places from an institution
const places = await sdk.getAvailablePlaces()
console.log("Places found:", places.length)
places.forEach((place) => {
console.log(`- ${place.title} (${place.mapvxId})`)
console.log(` Coordinates: ${place.position.lat}, ${place.position.lng}`)
console.log(` Description: ${place.description || "N/A"}`)
})
return places
} catch (error) {
console.error("Error loading places:", error)
throw error
}
}Load Subplaces
async function loadSubplaces(parentPlaceId: string) {
const sdk = initializeSDK("your-api-key", { lang: "en" })
try {
// Load subplaces from a specific parent place
const subplaces = await sdk.getSubPlaces(parentPlaceId)
console.log(`Subplaces of ${parentPlaceId}:`, subplaces.length)
subplaces.forEach((subplace) => {
console.log(`- ${subplace.title}`)
console.log(` ID: ${subplace.mapvxId}`)
console.log(` Floor: ${subplace.floor || "N/A"}`)
console.log(` Category: ${subplace.category || "N/A"}`)
})
return subplaces
} catch (error) {
console.error("Error loading subplaces:", error)
throw error
}
}Practical Integration Examples
Example 1: Dynamic Places Selector
class PlaceSelector {
private sdk: MapVXSDK
private selectElement: HTMLSelectElement
constructor(selectElement: HTMLSelectElement) {
this.selectElement = selectElement
}
async initialize(institutionId: string) {
this.sdk = initializeSDK("your-api-key", { lang: "en" })
await this.loadPlacesIntoSelector(institutionId)
}
async loadPlacesIntoSelector(institutionId: string) {
try {
// Clear selector
this.selectElement.innerHTML = '<option value="">Select a place...</option>'
// Load places
const places = await this.sdk.getAvailablePlaces()
// Add options to selector
places.forEach((place) => {
const option = document.createElement("option")
option.value = place.mapvxId
option.textContent = place.title || place.mapvxId
this.selectElement.appendChild(option)
})
console.log(`${places.length} places loaded into selector`)
} catch (error) {
console.error("Error loading places:", error)
this.selectElement.innerHTML = '<option value="">Error loading places</option>'
}
}
async loadSubplacesForParent(parentPlaceId: string) {
try {
const subplaces = await this.sdk.getSubPlaces(parentPlaceId)
// Clear and load subplaces
this.selectElement.innerHTML = '<option value="">Select a subplace...</option>'
subplaces.forEach((subplace) => {
const option = document.createElement("option")
option.value = subplace.mapvxId
option.textContent = `${subplace.title} ${subplace.floor ? `(Floor ${subplace.floor})` : ""}`
this.selectElement.appendChild(option)
})
console.log(`${subplaces.length} subplaces loaded`)
} catch (error) {
console.error("Error loading subplaces:", error)
}
}
}
// Usage
const placeSelector = new PlaceSelector(document.getElementById("placeSelect") as HTMLSelectElement)
placeSelector.initialize("your-institution-id")Example 2: Map with Conditional Initialization
class ConditionalMapLoader {
private sdk: MapVXSDK
private map: MapVXMap | null = null
async initializeWithParentPlace(parentPlaceId: string, institutionId: string) {
this.sdk = initializeSDK("your-api-key", { lang: "en" })
const mapContainer = document.getElementById("mapContainer") as HTMLElement
try {
// Initialize map with specific parent place
this.map = await this.sdk.createMap(mapContainer, {
parentPlaceId,
institutionId,
})
// Load subplaces from parent place
const subplaces = await this.sdk.getSubPlaces(parentPlaceId)
console.log(`Map created with ${subplaces.length} available subplaces`)
return { map: this.map, places: subplaces }
} catch (error) {
console.error("Error with parent place, trying without parent place...")
return await this.initializeWithoutParentPlace(institutionId)
}
}
async initializeWithoutParentPlace(institutionId: string) {
const mapContainer = document.getElementById("mapContainer") as HTMLElement
try {
// Initialize map without specific parent place
this.map = await this.sdk.createMap(mapContainer, { institutionId })
// Load general available places
const places = await this.sdk.getAvailablePlaces()
console.log(`Map created with ${places.length} available places`)
return { map: this.map, places: places }
} catch (error) {
console.error("Error initializing map:", error)
throw error
}
}
async smartInitialize(institutionId: string, preferredParentId?: string) {
if (preferredParentId) {
try {
return await this.initializeWithParentPlace(preferredParentId, institutionId)
} catch (error) {
console.warn("Parent place not available, using general configuration")
}
}
return await this.initializeWithoutParentPlace(institutionId)
}
}
// Usage
const mapLoader = new ConditionalMapLoader()
// Try with preferred parent place, fallback to general configuration
mapLoader
.smartInitialize("institution-id", "preferred-parent-id")
.then(({ map, places }) => {
console.log("Map initialized successfully")
console.log(`${places.length} places available`)
})
.catch((error) => {
console.error("Fatal error:", error)
})Example 3: Places Search and Filtering
class PlaceSearchManager {
private allPlaces: any[] = []
private filteredPlaces: any[] = []
async loadAndIndexPlaces(institutionId: string, parentPlaceId?: string) {
const sdk = initializeSDK("your-api-key", { lang: "en" })
try {
if (parentPlaceId) {
// Load subplaces if there's a parent place
this.allPlaces = await sdk.getSubPlaces(parentPlaceId)
} else {
// Load general available places
this.allPlaces = await sdk.getAvailablePlaces()
}
this.filteredPlaces = [...this.allPlaces]
console.log(`${this.allPlaces.length} places loaded and indexed`)
return this.allPlaces
} catch (error) {
console.error("Error loading places:", error)
throw error
}
}
searchPlaces(query: string): any[] {
if (!query.trim()) {
this.filteredPlaces = [...this.allPlaces]
return this.filteredPlaces
}
const searchTerm = query.toLowerCase().trim()
this.filteredPlaces = this.allPlaces.filter((place) => {
const title = (place.title || "").toLowerCase()
const description = (place.description || "").toLowerCase()
const category = (place.category || "").toLowerCase()
return (
title.includes(searchTerm) ||
description.includes(searchTerm) ||
category.includes(searchTerm)
)
})
console.log(`Search "${query}": ${this.filteredPlaces.length} results`)
return this.filteredPlaces
}
filterByCategory(category: string): any[] {
if (!category) {
this.filteredPlaces = [...this.allPlaces]
return this.filteredPlaces
}
this.filteredPlaces = this.allPlaces.filter((place) => place.category === category)
console.log(`Category filter "${category}": ${this.filteredPlaces.length} results`)
return this.filteredPlaces
}
getUniqueCategories(): string[] {
const categories = this.allPlaces
.map((place) => place.category)
.filter((category) => category)
.filter((category, index, array) => array.indexOf(category) === index)
return categories.sort()
}
}
// Usage with search interface
const searchManager = new PlaceSearchManager()
const searchInput = document.getElementById("searchInput") as HTMLInputElement
const categorySelect = document.getElementById("categorySelect") as HTMLSelectElement
const resultsList = document.getElementById("resultsList") as HTMLElement
// Initialize
searchManager.loadAndIndexPlaces("institution-id", "optional-parent-id").then(() => {
// Populate category filter
const categories = searchManager.getUniqueCategories()
categories.forEach((category) => {
const option = document.createElement("option")
option.value = category
option.textContent = category
categorySelect.appendChild(option)
})
})
// Search functionality
searchInput.addEventListener("input", (e) => {
const query = (e.target as HTMLInputElement).value
const results = searchManager.searchPlaces(query)
displayResults(results)
})
// Category filter
categorySelect.addEventListener("change", (e) => {
const category = (e.target as HTMLSelectElement).value
const results = searchManager.filterByCategory(category)
displayResults(results)
})
function displayResults(places: any[]) {
resultsList.innerHTML = places
.map(
(place) => `
<div class="place-item" data-id="${place.mapvxId}">
<h3>${place.title}</h3>
<p>${place.description || "No description"}</p>
<span class="category">${place.category || "No category"}</span>
</div>
`
)
.join("")
}Place Object Properties
Typical structure of a place
interface MVXPlace {
mapvxId: string // Unique place ID
title?: string // Place name
description?: string // Description
position: {
// Coordinates
lat: number
lng: number
}
category?: string // Place category
floor?: string | number // Floor (for subplaces)
parentPlaceId?: string // Parent place ID
institutionId?: string // Institution ID
metadata?: {
// Additional data
[key: string]: any
}
}Common Error Handling
async function robustDataLoading(institutionId: string, parentPlaceId?: string) {
try {
const sdk = initializeSDK("your-api-key", { lang: "en" })
let places: any[] = []
if (parentPlaceId) {
try {
places = await sdk.getSubPlaces(parentPlaceId)
console.log(`Subplaces loaded: ${places.length}`)
} catch (subplaceError) {
console.warn("Error loading subplaces, trying general places:", subplaceError)
places = await sdk.getAvailablePlaces()
console.log(`General places loaded: ${places.length}`)
}
} else {
places = await sdk.getAvailablePlaces()
console.log(`Available places loaded: ${places.length}`)
}
if (places.length === 0) {
console.warn("No places found to display")
}
return places
} catch (error) {
console.error("Fatal error loading data:", error)
if (error.message.includes("unauthorized")) {
throw new Error("Invalid API key or no permissions")
} else if (error.message.includes("not found")) {
throw new Error("Institution or place not found")
} else {
throw new Error("Connection error or service unavailable")
}
}
}Best Practices
- Enable Persistent Caching: Use the SDK’s built-in cache system for better performance
- Loading States: Show loading indicators while fetching data
- Fallback Logic: Have backup logic if subplace loading fails
- Error Boundaries: Handle network and permission errors appropriately
- Lazy Loading: Load data on demand when possible
Caching Best Practices
The SDK includes a built-in caching system that can significantly improve performance. Here are recommendations for optimal cache configuration:
Enabling Persistent Cache
For applications where users frequently return (like kiosks or directories), enable persistent caching:
const sdk = initializeSDK("your-api-key", {
lang: "en",
cache: {
persistent: true, // Data survives page reloads
},
})Optimizing Cache for Your Use Case
Different applications have different caching needs:
// For high-traffic directory applications
const sdk = initializeSDK("your-api-key", {
lang: "en",
cache: {
persistent: true,
maxStorageBytes: 10 * 1024 * 1024, // 10MB for large venues
configs: {
places: {
maxItems: 2000, // Large venue with many places
ttlMs: 60 * 60 * 1000, // 1 hour - places don't change often
},
subPlaces: {
maxItems: 500,
ttlMs: 30 * 60 * 1000, // 30 minutes
},
routes: {
maxItems: 300, // More routes for busy applications
ttlMs: 15 * 60 * 1000, // 15 minutes - routes are more dynamic
},
configurations: {
maxItems: 20,
ttlMs: 2 * 60 * 60 * 1000, // 2 hours - configurations rarely change
},
},
},
})Using Cached Data
The SDK automatically uses cached data when available. You can also access cached places directly:
// Get places that are already in cache (no network request)
const cachedPlaces = sdk.getCachedPlaces(["place-id-1", "place-id-2", "place-id-3"])
console.log(`Found ${cachedPlaces.length} places in cache`)Cache Invalidation
When you need fresh data, clear the routes cache:
// Clear cached routes when you know data has changed
sdk.cleanRoutesCache()
// Then fetch fresh route data
const freshRoute = await sdk.getRoute(routeConfig)Example: Data Loading with Cache Strategy
class OptimizedDataLoader {
private sdk: MapVXSDK
async initialize() {
this.sdk = initializeSDK("your-api-key", {
lang: "en",
cache: {
persistent: true,
configs: {
places: { maxItems: 1000, ttlMs: 60 * 60 * 1000 },
},
},
})
}
async loadPlaces(institutionId: string): Promise<MVXPlace[]> {
try {
// SDK automatically uses cache if available
const places = await this.sdk.getAvailablePlaces()
console.log(`Loaded ${places.length} places`)
return places
} catch (error) {
console.error("Error loading places:", error)
throw error
}
}
// Use cached data for quick lookups
getPlaceFromCache(placeIds: string[]): MVXPlace[] {
return this.sdk.getCachedPlaces(placeIds)
}
}Last updated on