feat: implement billing, usage tracking, and administrative geocoding features in API with infrastructure updates
This commit is contained in:
Vendored
+122
-22
@@ -14,6 +14,7 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
var _a, _b, _c;
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.MapsService = void 0;
|
||||
const common_1 = require("@nestjs/common");
|
||||
@@ -24,20 +25,29 @@ const axios_1 = __importDefault(require("axios"));
|
||||
const road_stat_entity_1 = require("./road-stat.entity");
|
||||
const traffic_grid_service_1 = require("./traffic-grid.service");
|
||||
const geocoding_service_1 = require("../geocoding/geocoding.service");
|
||||
const redis_service_1 = require("../common/redis.service");
|
||||
let MapsService = class MapsService {
|
||||
configService;
|
||||
roadStatRepo;
|
||||
trafficGrid;
|
||||
geocodingService;
|
||||
dataSource;
|
||||
redisService;
|
||||
graphHopperUrl;
|
||||
constructor(configService, roadStatRepo, trafficGrid, geocodingService) {
|
||||
constructor(configService, roadStatRepo, trafficGrid, geocodingService, dataSource, redisService) {
|
||||
this.configService = configService;
|
||||
this.roadStatRepo = roadStatRepo;
|
||||
this.trafficGrid = trafficGrid;
|
||||
this.geocodingService = geocodingService;
|
||||
this.dataSource = dataSource;
|
||||
this.redisService = redisService;
|
||||
this.graphHopperUrl = this.configService.get('GRAPH_HOPPER_URL', 'http://routing:8080');
|
||||
}
|
||||
async getRoute(waypoints, profile = 'car') {
|
||||
async requestRoutingSync() {
|
||||
await this.redisService.set('routing_sync_requested', '1');
|
||||
return { success: true, message: 'Routing sync requested and scheduled for the next minute.' };
|
||||
}
|
||||
async getRoute(waypoints, profile = 'car', steps = false, locale = 'en', alternatives = false) {
|
||||
if (waypoints.length < 2) {
|
||||
throw new common_1.HttpException('At least two waypoints are required', common_1.HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
@@ -68,19 +78,69 @@ let MapsService = class MapsService {
|
||||
const payload = {
|
||||
points: ghPoints,
|
||||
profile: profile,
|
||||
locale: 'en',
|
||||
locale: locale === 'en' ? 'ar' : locale,
|
||||
calc_points: true,
|
||||
points_encoded: true,
|
||||
instructions: steps || true,
|
||||
};
|
||||
if (waypoints.length === 2) {
|
||||
let closureCount = 0;
|
||||
try {
|
||||
const closedSegments = await this.dataSource.query(`
|
||||
SELECT ST_AsGeoJSON(
|
||||
ST_Transform(ST_Buffer(ST_Transform(geometry::geometry, 3857), 15), 4326), 6
|
||||
) AS geojson
|
||||
FROM road_segment_stats
|
||||
WHERE "isClosed" = true AND geometry IS NOT NULL
|
||||
LIMIT 50
|
||||
`);
|
||||
const features = closedSegments
|
||||
.map((s, i) => {
|
||||
try {
|
||||
return { type: 'Feature', id: `closed_${i}`, geometry: JSON.parse(s.geojson), properties: {} };
|
||||
}
|
||||
catch {
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.filter(Boolean);
|
||||
if (features.length > 0) {
|
||||
payload['custom_model'] = {
|
||||
priority: features.map((f) => ({ if: `in_${f.id}`, multiply_by: '0' })),
|
||||
areas: { type: 'FeatureCollection', features },
|
||||
};
|
||||
payload['ch.disable'] = true;
|
||||
closureCount = features.length;
|
||||
console.log(`🚧 Routing: avoiding ${closureCount} closed segment(s).`);
|
||||
}
|
||||
}
|
||||
catch (closureError) {
|
||||
console.warn('⚠️ Could not load road closures for routing:', closureError.message);
|
||||
}
|
||||
if (alternatives && waypoints.length === 2) {
|
||||
payload.algorithm = 'alternative_route';
|
||||
payload['ch.disable'] = true;
|
||||
payload['alternative_route.max_paths'] = 2;
|
||||
payload['alternative_route.max_weight_factor'] = 1.6;
|
||||
payload['alternative_route.max_share_factor'] = 0.6;
|
||||
}
|
||||
console.log(`Routing Request: ${waypoints.length} points via ${profile} on ${this.graphHopperUrl}`);
|
||||
const response = await axios_1.default.post(`${this.graphHopperUrl}/route`, payload, { timeout: 10000 });
|
||||
console.log(`Routing Request: ${waypoints.length} points via ${profile} on ${this.graphHopperUrl} | Steps: ${steps} | Locale: ${locale}`);
|
||||
let response;
|
||||
try {
|
||||
response = await axios_1.default.post(`${this.graphHopperUrl}/route`, payload, { timeout: 10000 });
|
||||
}
|
||||
catch (routeErr) {
|
||||
if (closureCount > 0 && payload['custom_model']) {
|
||||
const detail = routeErr.response ? JSON.stringify(routeErr.response.data) : routeErr.message;
|
||||
console.warn(`⚠️ Closure-aware routing failed (${detail}). Retrying without closures...`);
|
||||
delete payload['custom_model'];
|
||||
if (!(alternatives && waypoints.length === 2))
|
||||
delete payload['ch.disable'];
|
||||
response = await axios_1.default.post(`${this.graphHopperUrl}/route`, payload, { timeout: 10000 });
|
||||
}
|
||||
else {
|
||||
throw routeErr;
|
||||
}
|
||||
}
|
||||
console.log('Routing SUCCESS');
|
||||
const paths = response.data.paths;
|
||||
if (!paths || paths.length === 0)
|
||||
@@ -93,22 +153,45 @@ let MapsService = class MapsService {
|
||||
const trafficFactor = this.trafficGrid.getTrafficFactor(coords, hr, dow);
|
||||
const baseDuration = route.time / 1000;
|
||||
const trafficAwareDuration = baseDuration * trafficFactor;
|
||||
const alternatives = paths.slice(1).map(alt => ({
|
||||
distance: alt.distance,
|
||||
duration: Math.round(alt.time / 1000),
|
||||
points: alt.points,
|
||||
bbox: alt.bbox
|
||||
}));
|
||||
const processedPaths = paths.map((p, index) => {
|
||||
const pCoords = this.decodePolyline(p.points);
|
||||
const pTrafficFactor = this.trafficGrid.getTrafficFactor(pCoords, hr, dow);
|
||||
const pDuration = Math.round((p.time / 1000) * pTrafficFactor);
|
||||
const routeName = this.getRouteName(p.instructions);
|
||||
const tags = [];
|
||||
if (index === 0)
|
||||
tags.push('FASTEST');
|
||||
if (paths.length > 1) {
|
||||
const isShortest = paths.every((other) => p.distance <= other.distance);
|
||||
if (isShortest)
|
||||
tags.push('SHORTEST');
|
||||
if (index > 0 && !isShortest)
|
||||
tags.push('ALTERNATIVE');
|
||||
}
|
||||
return {
|
||||
routeName: routeName ? `عبر ${routeName}` : `المسار ${index + 1}`,
|
||||
tags,
|
||||
distance: p.distance,
|
||||
duration: pDuration,
|
||||
points: p.points,
|
||||
bbox: p.bbox,
|
||||
instructions: steps ? p.instructions : undefined
|
||||
};
|
||||
});
|
||||
const mainRoute = processedPaths[0];
|
||||
const altRoutes = processedPaths.slice(1);
|
||||
return {
|
||||
distance: route.distance,
|
||||
duration: Math.round(baseDuration),
|
||||
trafficAwareDuration: Math.round(trafficAwareDuration),
|
||||
routeName: mainRoute.routeName,
|
||||
tags: mainRoute.tags,
|
||||
distance: mainRoute.distance,
|
||||
duration: mainRoute.duration,
|
||||
trafficFactor: Math.round(trafficFactor * 100) / 100,
|
||||
startName,
|
||||
endName,
|
||||
points: route.points,
|
||||
bbox: route.bbox,
|
||||
alternatives: alternatives
|
||||
points: mainRoute.points,
|
||||
bbox: mainRoute.bbox,
|
||||
instructions: mainRoute.instructions,
|
||||
alternatives: altRoutes
|
||||
};
|
||||
}
|
||||
catch (error) {
|
||||
@@ -117,6 +200,25 @@ let MapsService = class MapsService {
|
||||
throw new common_1.HttpException(`Routing Failure: ${msg}`, common_1.HttpStatus.BAD_GATEWAY);
|
||||
}
|
||||
}
|
||||
getRouteName(instructions) {
|
||||
if (!instructions || instructions.length === 0)
|
||||
return null;
|
||||
const streetDistances = {};
|
||||
for (const inst of instructions) {
|
||||
if (inst.street_name && inst.street_name.trim() !== '') {
|
||||
streetDistances[inst.street_name] = (streetDistances[inst.street_name] || 0) + (inst.distance || 0);
|
||||
}
|
||||
}
|
||||
let longestStreet = null;
|
||||
let maxDist = 0;
|
||||
for (const [street, dist] of Object.entries(streetDistances)) {
|
||||
if (dist > maxDist) {
|
||||
maxDist = dist;
|
||||
longestStreet = street;
|
||||
}
|
||||
}
|
||||
return longestStreet;
|
||||
}
|
||||
decodePolyline(encoded) {
|
||||
const points = [];
|
||||
let index = 0, len = encoded.length;
|
||||
@@ -155,9 +257,7 @@ exports.MapsService = MapsService;
|
||||
exports.MapsService = MapsService = __decorate([
|
||||
(0, common_1.Injectable)(),
|
||||
__param(1, (0, typeorm_1.InjectRepository)(road_stat_entity_1.RoadSegmentStat)),
|
||||
__metadata("design:paramtypes", [config_1.ConfigService,
|
||||
typeorm_2.Repository,
|
||||
traffic_grid_service_1.TrafficGridService,
|
||||
geocoding_service_1.GeocodingService])
|
||||
__metadata("design:paramtypes", [typeof (_a = typeof config_1.ConfigService !== "undefined" && config_1.ConfigService) === "function" ? _a : Object, typeof (_b = typeof typeorm_2.Repository !== "undefined" && typeorm_2.Repository) === "function" ? _b : Object, traffic_grid_service_1.TrafficGridService,
|
||||
geocoding_service_1.GeocodingService, typeof (_c = typeof typeorm_2.DataSource !== "undefined" && typeorm_2.DataSource) === "function" ? _c : Object, redis_service_1.RedisService])
|
||||
], MapsService);
|
||||
//# sourceMappingURL=maps.service.js.map
|
||||
Reference in New Issue
Block a user