fix(mapillary): always sub-sample small bbox + retry on transient errors

This commit is contained in:
tiennm99 committed 2026-05-10 00:40:47 +07:00
1 parent 963175886f
commit e47b7fe28b
1 file changed
+28 -32
+28 -32
View File
@@ -1,40 +1,34 @@
// Mapillary API utilities
// Maximum bbox area allowed by Mapillary API (square degrees).
// Mapillary tightened limits: keep area small enough to avoid 500 'reduce data' errors.
const MAX_BBOX_AREA = 0.001;
// Maximum bbox area allowed per Mapillary query (square degrees).
// Mapillary returns 500 'reduce data' / 'unknown error' on dense urban bboxes,
// so we keep each query small (~1km square) and rely on the retry loop to
// re-roll a new random sub-bbox if one query fails or returns empty.
const MAX_BBOX_AREA = 0.0001;
// Pick a random sub-bbox within the city bbox that fits Mapillary's area limit
// 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
// Always sub-sample even if the parent bbox is smaller than the cap — a
// smaller window keeps Mapillary happy in dense regions like central Hanoi.
const side = Math.sqrt(MAX_BBOX_AREA) * 0.9; // 10% safety margin
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);
const startLng = minLng + Math.random() * Math.max(0, width - subWidth);
const startLat = minLat + Math.random() * Math.max(0, height - subHeight);
return [startLng, startLat, startLng + subWidth, startLat + subHeight];
}
// Fetch images from a single bbox query
// 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_2048_url,geometry,is_pano&limit=5&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 }
@@ -54,37 +48,39 @@ async function queryMapillary(accessToken, queryBbox) {
return data.data || [];
}
// Fetch images using city bbox, retrying with different sub-bboxes
const MAX_RETRIES = 5;
// Fetch panoramic images, retrying with a fresh sub-bbox on failure or empty
// result. Auth failures still abort early.
const MAX_RETRIES = 8;
export async function fetchMapillaryImages(bbox) {
const accessToken = process.env.MAPILLARY_ACCESS_TOKEN;
if (!accessToken) {
throw new Error('MAPILLARY_ACCESS_TOKEN environment variable is not set');
}
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
try {
const queryBbox = getRandomSubBbox(bbox);
const images = await queryMapillary(accessToken, queryBbox);
let lastError = null;
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
const queryBbox = getRandomSubBbox(bbox);
try {
const images = await queryMapillary(accessToken, queryBbox);
if (images.length > 0) {
console.log(`Found ${images.length} images on attempt ${attempt}`);
return { success: true, data: images };
}
if (process.env.NODE_ENV === 'development') {
console.log(`Attempt ${attempt}/${MAX_RETRIES}: no images found, retrying...`);
}
console.log(`Attempt ${attempt}/${MAX_RETRIES}: no images, retrying with new sub-bbox`);
} catch (error) {
console.error(`Attempt ${attempt}/${MAX_RETRIES} failed:`, error.message);
throw error;
// Auth issues are not transient — abort fast.
if (error.message === 'Mapillary authentication failed') throw error;
lastError = error;
console.error(`Attempt ${attempt}/${MAX_RETRIES} failed: ${error.message}; retrying with new sub-bbox`);
}
}
return {
success: false,
error: 'No street view images found in city bbox'
error: lastError
? `No street view images found after ${MAX_RETRIES} attempts (last: ${lastError.message})`
: 'No street view images found in city bbox',
};
}