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:
tiennm99 committed 2026-04-04 09:10:20 +07:00
1 parent 9bee409f52
commit 5c2064dfca
2 files changed
+74 -48

No files matched your search

+2
View File
@@ -1,4 +1,6 @@
.idea
.claude/session-state
.claude/settings.local.json
+72 -48
View File
@@ -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'
};
}