import fs from "fs" // Load the actual Saudi Arabia borders from sa.json const saudiPolygon = JSON.parse(fs.readFileSync("sa.json", "utf8")) // Saudi Arabia accurate geographical bounds (for quick bounding box checks) // More precise boundaries to avoid points in neighboring countries const SAUDI_BOUNDS = { minLng: 36.0, // More conservative western boundary (Red Sea coast) maxLng: 55.0, // More conservative eastern boundary (Persian Gulf coast) minLat: 17.0, // More conservative southern boundary (Yemen border) maxLat: 32.0, // Northern boundary (Jordan/Iraq border) } // Point-in-polygon algorithm (ray casting) function pointInPolygon(point, polygon) { const [x, y] = point let inside = false for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) { const [xi, yi] = polygon[i] const [xj, yj] = polygon[j] if (yi > y !== yj > y && x < ((xj - xi) * (y - yi)) / (yj - yi) + xi) { inside = !inside } } return inside } // Check if point is inside any of the Saudi polygons function isPointInSaudiArabia(point) { const multiPolygon = saudiPolygon.features[0].geometry.coordinates for (const polygon of multiPolygon) { // Each polygon might have holes, first ring is exterior, others are holes const exteriorRing = polygon[0] if (pointInPolygon(point, exteriorRing)) { // Check if point is in any holes let inHole = false for (let i = 1; i < polygon.length; i++) { if (pointInPolygon(point, polygon[i])) { inHole = true break } } if (!inHole) { return true } } } return false } // Major Saudi cities for reference and better distribution (verified coordinates) const MAJOR_CITIES = [ { name: "Riyadh", lng: 46.6753, lat: 24.7136 }, { name: "Jeddah", lng: 39.2376, lat: 21.4858 }, { name: "Mecca", lng: 39.8579, lat: 21.3891 }, { name: "Medina", lng: 39.6118, lat: 24.5247 }, { name: "Dammam", lng: 50.088, lat: 26.4207 }, { name: "Khobar", lng: 50.208, lat: 26.2791 }, { name: "Tabuk", lng: 36.5951, lat: 28.3998 }, { name: "Buraidah", lng: 43.975, lat: 26.326 }, { name: "Khamis Mushait", lng: 42.7284, lat: 18.3057 }, { name: "Hail", lng: 41.69, lat: 27.5114 }, { name: "Hofuf", lng: 49.586, lat: 25.3491 }, { name: "Jubail", lng: 49.6603, lat: 27.0174 }, { name: "Abha", lng: 42.5058, lat: 18.2164 }, { name: "Yanbu", lng: 38.0618, lat: 24.0895 }, { name: "Najran", lng: 44.13, lat: 17.4924 }, { name: "Arar", lng: 41.0377, lat: 30.9753 }, { name: "Al Qatif", lng: 50.0089, lat: 26.5056 }, { name: "Sakaka", lng: 40.2062, lat: 29.9697 }, { name: "Al Bahah", lng: 41.4687, lat: 20.0129 }, ].filter((city) => isPointInSaudiArabia([city.lng, city.lat])) const MAKERS = [ "Maker A", "Maker B", "Maker C", "Maker D", "Maker E", "Maker F", "Maker G", "Maker H", "Maker I", "Maker J", ] function randomInRange(min, max) { return Math.random() * (max - min) + min } function isWithinSaudiBounds(lng, lat) { // First do a quick bounding box check for performance if ( lng < SAUDI_BOUNDS.minLng || lng > SAUDI_BOUNDS.maxLng || lat < SAUDI_BOUNDS.minLat || lat > SAUDI_BOUNDS.maxLat ) { return false } // Then do the precise polygon check return isPointInSaudiArabia([lng, lat]) } function generateCoordinatesAroundCity(city, radius = 0.3) { // Reduced radius for better accuracy let attempts = 0 let coordinates do { const angle = Math.random() * 2 * Math.PI const distance = Math.random() * radius const lng = city.lng + distance * Math.cos(angle) const lat = city.lat + distance * Math.sin(angle) coordinates = [lng, lat] attempts++ } while ( !isWithinSaudiBounds(coordinates[0], coordinates[1]) && attempts < 20 ) // If we can't find a valid coordinate around the city, use the city coordinates if (!isWithinSaudiBounds(coordinates[0], coordinates[1])) { coordinates = [city.lng, city.lat] } return coordinates } function generateRandomCoordinates() { let attempts = 0 let coordinates do { coordinates = [ randomInRange(SAUDI_BOUNDS.minLng, SAUDI_BOUNDS.maxLng), randomInRange(SAUDI_BOUNDS.minLat, SAUDI_BOUNDS.maxLat), ] attempts++ } while ( !isWithinSaudiBounds(coordinates[0], coordinates[1]) && attempts < 20 ) return coordinates } function generateMeterFeature(id, coordinates) { const maker = MAKERS[Math.floor(Math.random() * MAKERS.length)] const model = `Model ${id}` return { type: "Feature", geometry: { type: "Point", coordinates: coordinates, }, properties: { id: `meter-${id}`, maker: maker, model: model, timestamp: "2025-08-13T11:47:00Z", }, } } function generateMetersData(totalPoints = 1000) { const features = [] console.log(`Valid major cities within Saudi borders: ${MAJOR_CITIES.length}`) // Generate 60% of points around major cities for realistic distribution const cityPoints = Math.floor(totalPoints * 0.6) const randomPoints = totalPoints - cityPoints console.log(`Generating ${cityPoints} points around major cities...`) for (let i = 1; i <= cityPoints; i++) { const city = MAJOR_CITIES[Math.floor(Math.random() * MAJOR_CITIES.length)] const coordinates = generateCoordinatesAroundCity(city, 0.3) // Smaller radius for accuracy features.push(generateMeterFeature(i, coordinates)) } console.log(`Generating ${randomPoints} random points across Saudi Arabia...`) for (let i = cityPoints + 1; i <= totalPoints; i++) { const coordinates = generateRandomCoordinates() features.push(generateMeterFeature(i, coordinates)) } // Validate all coordinates are within actual Saudi borders const validFeatures = features.filter((feature) => { const [lng, lat] = feature.geometry.coordinates return isWithinSaudiBounds(lng, lat) }) console.log( `Generated ${features.length} features, ${validFeatures.length} are within actual Saudi borders` ) // If we have fewer than desired points, generate more around cities if (validFeatures.length < totalPoints) { const needed = totalPoints - validFeatures.length console.log(`Generating ${needed} additional points around major cities...`) for (let i = 0; i < needed; i++) { const city = MAJOR_CITIES[Math.floor(Math.random() * MAJOR_CITIES.length)] const coordinates = generateCoordinatesAroundCity(city, 0.2) // Even smaller radius const newFeature = generateMeterFeature( validFeatures.length + i + 1, coordinates ) validFeatures.push(newFeature) } } return { type: "FeatureCollection", features: validFeatures.slice(0, totalPoints), // Ensure exactly the requested number } } // Generate the data const points = parseInt(process.env.POINTS, 10) || (process.argv[2] ? parseInt(process.argv[2], 10) : 100000) console.log( `Generating ${points} meter points across Saudi Arabia using actual borders...` ) const metersData = generateMetersData(points) // Write to file fs.writeFileSync("saudi_meters.json", JSON.stringify(metersData, null, 2)) console.log( `βœ… Successfully generated ${metersData.features.length} meter points!` ) console.log("πŸ“ Distribution:") console.log(" - 60% around major cities") console.log(" - 40% randomly distributed") console.log( "πŸ—ΊοΈ Coverage: Entire Saudi Arabia territory (using actual polygon borders)" ) // Final validation const invalidPoints = metersData.features.filter((feature) => { const [lng, lat] = feature.geometry.coordinates return !isPointInSaudiArabia([lng, lat]) }) console.log( `\nπŸ” Final validation: ${invalidPoints.length} points outside Saudi borders` ) if (invalidPoints.length === 0) { console.log("βœ… All points are within Saudi Arabia's actual borders!") }