mirror of
https://github.com/tiennm99/vngeoguessr.git
synced 2026-10-16 02:15:59 +00:00
fix(mapillary): subdivide large bboxes to fit API area limit
Mapillary API rejects bboxes exceeding 0.01 sq degrees with HTTP 500. City bboxes (e.g. TPHCM=0.016) exceeded this limit. Now picks random sub-bboxes within the city area and retries up to 5 times to find coverage.
This commit is contained in:
1 parent
9bee409f52
commit
5c2064dfca
2 files changed
+74
-48
No files matched your search
@@ -1,4 +1,6 @@
|
||||
.idea
|
||||
.claude/session-state
|
||||
.claude/settings.local.json
|
||||
|
||||
|
||||
|
||||
|
||||
+72
-48
@@ -1,6 +1,58 @@
|
||||
// Mapillary API utilities
|
||||
|
||||
// Fetch images using city bbox
|
||||
// Maximum bbox area allowed by Mapillary API (square degrees)
|
||||
const MAX_BBOX_AREA = 0.01;
|
||||
|
||||
// Pick a random sub-bbox within the city bbox that fits Mapillary's area limit
|
||||
function getRandomSubBbox(bbox) {
|
||||
const [minLng, minLat, maxLng, maxLat] = bbox;
|
||||
const width = maxLng - minLng;
|
||||
const height = maxLat - minLat;
|
||||
const area = width * height;
|
||||
|
||||
if (area <= MAX_BBOX_AREA) {
|
||||
return bbox;
|
||||
}
|
||||
|
||||
// Calculate sub-bbox side length (square) that fits within the limit
|
||||
const side = Math.sqrt(MAX_BBOX_AREA) * 0.9; // 10% margin for safety
|
||||
const subWidth = Math.min(side, width);
|
||||
const subHeight = Math.min(side, height);
|
||||
|
||||
const startLng = minLng + Math.random() * (width - subWidth);
|
||||
const startLat = minLat + Math.random() * (height - subHeight);
|
||||
|
||||
return [startLng, startLat, startLng + subWidth, startLat + subHeight];
|
||||
}
|
||||
|
||||
// Fetch images from a single bbox query
|
||||
async function queryMapillary(accessToken, queryBbox) {
|
||||
const bboxString = queryBbox.join(',');
|
||||
const apiUrl = `https://graph.mapillary.com/images?access_token=${accessToken}&fields=id,thumb_original_url,geometry,is_pano&limit=20&bbox=${bboxString}&is_pano=true`;
|
||||
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.log('Mapillary query bbox:', bboxString);
|
||||
}
|
||||
|
||||
const response = await fetch(apiUrl, {
|
||||
headers: { 'Accept': 'application/json' },
|
||||
next: { revalidate: 0 }
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
if (response.status === 401) {
|
||||
throw new Error('Mapillary authentication failed');
|
||||
}
|
||||
throw new Error(`Mapillary API error: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data.data || [];
|
||||
}
|
||||
|
||||
// Fetch images using city bbox, retrying with different sub-bboxes
|
||||
const MAX_RETRIES = 5;
|
||||
|
||||
export async function fetchMapillaryImages(bbox) {
|
||||
const accessToken = process.env.MAPILLARY_ACCESS_TOKEN;
|
||||
|
||||
@@ -8,55 +60,27 @@ export async function fetchMapillaryImages(bbox) {
|
||||
throw new Error('MAPILLARY_ACCESS_TOKEN environment variable is not set');
|
||||
}
|
||||
|
||||
const bboxString = bbox.join(',');
|
||||
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
||||
try {
|
||||
const queryBbox = getRandomSubBbox(bbox);
|
||||
const images = await queryMapillary(accessToken, queryBbox);
|
||||
|
||||
const apiUrl = `https://graph.mapillary.com/images?access_token=${accessToken}&fields=id,thumb_original_url,geometry,is_pano&limit=20&bbox=${bboxString}&is_pano=true`;
|
||||
|
||||
// Log API request details in development
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.log('=== MAPILLARY API REQUEST ===');
|
||||
console.log('Request URL:', apiUrl.replace(accessToken, '[HIDDEN]'));
|
||||
console.log('Bbox:', bboxString);
|
||||
console.log('=== END REQUEST ===');
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(apiUrl, {
|
||||
headers: { 'Accept': 'application/json' },
|
||||
next: { revalidate: 0 }
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
if (response.status === 401) {
|
||||
throw new Error('Mapillary authentication failed');
|
||||
if (images.length > 0) {
|
||||
console.log(`Found ${images.length} images on attempt ${attempt}`);
|
||||
return { success: true, data: images };
|
||||
}
|
||||
throw new Error(`Mapillary API error: ${response.status}`);
|
||||
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.log(`Attempt ${attempt}/${MAX_RETRIES}: no images found, retrying...`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Attempt ${attempt}/${MAX_RETRIES} failed:`, error.message);
|
||||
throw error;
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
// Log full Mapillary response in development
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.log('=== MAPILLARY API RESPONSE ===');
|
||||
console.log('Full response:', JSON.stringify(data, null, 2));
|
||||
console.log('=== END MAPILLARY RESPONSE ===');
|
||||
}
|
||||
|
||||
if (data.data && data.data.length > 0) {
|
||||
console.log(`Found ${data.data.length} images within city bbox`);
|
||||
return {
|
||||
success: true,
|
||||
data: data.data
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: 'No street view images found in city bbox'
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error fetching Mapillary images:', error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: 'No street view images found in city bbox'
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user