mirror of
https://github.com/immich-app/immich.git
synced 2024-11-15 09:59:00 -07:00
feat(server): reverse geocoding endpoint (#11430)
* feat(server): reverse geocoding endpoint * chore: rename error message
This commit is contained in:
parent
a70cd368af
commit
ebc71e428d
@ -159,4 +159,75 @@ describe('/map', () => {
|
||||
expect(body).toEqual(expect.objectContaining({ id: 'immich-map-dark' }));
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /map/reverse-geocode', () => {
|
||||
it('should require authentication', async () => {
|
||||
const { status, body } = await request(app).get('/map/reverse-geocode');
|
||||
expect(status).toBe(401);
|
||||
expect(body).toEqual(errorDto.unauthorized);
|
||||
});
|
||||
|
||||
it('should throw an error if a lat is not provided', async () => {
|
||||
const { status, body } = await request(app)
|
||||
.get('/map/reverse-geocode?lon=123')
|
||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(errorDto.badRequest(['lat must be a number between -90 and 90']));
|
||||
});
|
||||
|
||||
it('should throw an error if a lat is not a number', async () => {
|
||||
const { status, body } = await request(app)
|
||||
.get('/map/reverse-geocode?lat=abc&lon=123.456')
|
||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(errorDto.badRequest(['lat must be a number between -90 and 90']));
|
||||
});
|
||||
|
||||
it('should throw an error if a lat is out of range', async () => {
|
||||
const { status, body } = await request(app)
|
||||
.get('/map/reverse-geocode?lat=91&lon=123.456')
|
||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(errorDto.badRequest(['lat must be a number between -90 and 90']));
|
||||
});
|
||||
|
||||
it('should throw an error if a lon is not provided', async () => {
|
||||
const { status, body } = await request(app)
|
||||
.get('/map/reverse-geocode?lat=75')
|
||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(errorDto.badRequest(['lon must be a number between -180 and 180']));
|
||||
});
|
||||
|
||||
const reverseGeocodeTestCases = [
|
||||
{
|
||||
name: 'Vaucluse',
|
||||
lat: -33.858_977_058_663_13,
|
||||
lon: 151.278_490_730_270_48,
|
||||
results: [{ city: 'Vaucluse', state: 'New South Wales', country: 'Australia' }],
|
||||
},
|
||||
{
|
||||
name: 'Ravenhall',
|
||||
lat: -37.765_732_399_174_75,
|
||||
lon: 144.752_453_164_883_3,
|
||||
results: [{ city: 'Ravenhall', state: 'Victoria', country: 'Australia' }],
|
||||
},
|
||||
{
|
||||
name: 'Scarborough',
|
||||
lat: -31.894_346_156_789_997,
|
||||
lon: 115.757_617_103_904_64,
|
||||
results: [{ city: 'Scarborough', state: 'Western Australia', country: 'Australia' }],
|
||||
},
|
||||
];
|
||||
|
||||
it.each(reverseGeocodeTestCases)(`should resolve to $name`, async ({ lat, lon, results }) => {
|
||||
const { status, body } = await request(app)
|
||||
.get(`/map/reverse-geocode?lat=${lat}&lon=${lon}`)
|
||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||
expect(status).toBe(200);
|
||||
expect(Array.isArray(body)).toBe(true);
|
||||
expect(body.length).toBe(results.length);
|
||||
expect(body).toEqual(results);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
2
mobile/openapi/README.md
generated
2
mobile/openapi/README.md
generated
@ -146,6 +146,7 @@ Class | Method | HTTP request | Description
|
||||
*LibrariesApi* | [**validate**](doc//LibrariesApi.md#validate) | **POST** /libraries/{id}/validate |
|
||||
*MapApi* | [**getMapMarkers**](doc//MapApi.md#getmapmarkers) | **GET** /map/markers |
|
||||
*MapApi* | [**getMapStyle**](doc//MapApi.md#getmapstyle) | **GET** /map/style.json |
|
||||
*MapApi* | [**reverseGeocode**](doc//MapApi.md#reversegeocode) | **GET** /map/reverse-geocode |
|
||||
*MemoriesApi* | [**addMemoryAssets**](doc//MemoriesApi.md#addmemoryassets) | **PUT** /memories/{id}/assets |
|
||||
*MemoriesApi* | [**createMemory**](doc//MemoriesApi.md#creatememory) | **POST** /memories |
|
||||
*MemoriesApi* | [**deleteMemory**](doc//MemoriesApi.md#deletememory) | **DELETE** /memories/{id} |
|
||||
@ -339,6 +340,7 @@ Class | Method | HTTP request | Description
|
||||
- [LoginResponseDto](doc//LoginResponseDto.md)
|
||||
- [LogoutResponseDto](doc//LogoutResponseDto.md)
|
||||
- [MapMarkerResponseDto](doc//MapMarkerResponseDto.md)
|
||||
- [MapReverseGeocodeResponseDto](doc//MapReverseGeocodeResponseDto.md)
|
||||
- [MapTheme](doc//MapTheme.md)
|
||||
- [MemoryCreateDto](doc//MemoryCreateDto.md)
|
||||
- [MemoryLaneResponseDto](doc//MemoryLaneResponseDto.md)
|
||||
|
1
mobile/openapi/lib/api.dart
generated
1
mobile/openapi/lib/api.dart
generated
@ -152,6 +152,7 @@ part 'model/login_credential_dto.dart';
|
||||
part 'model/login_response_dto.dart';
|
||||
part 'model/logout_response_dto.dart';
|
||||
part 'model/map_marker_response_dto.dart';
|
||||
part 'model/map_reverse_geocode_response_dto.dart';
|
||||
part 'model/map_theme.dart';
|
||||
part 'model/memory_create_dto.dart';
|
||||
part 'model/memory_lane_response_dto.dart';
|
||||
|
57
mobile/openapi/lib/api/map_api.dart
generated
57
mobile/openapi/lib/api/map_api.dart
generated
@ -160,4 +160,61 @@ class MapApi {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Performs an HTTP 'GET /map/reverse-geocode' operation and returns the [Response].
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [double] lat (required):
|
||||
///
|
||||
/// * [double] lon (required):
|
||||
Future<Response> reverseGeocodeWithHttpInfo(double lat, double lon,) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final path = r'/map/reverse-geocode';
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody;
|
||||
|
||||
final queryParams = <QueryParam>[];
|
||||
final headerParams = <String, String>{};
|
||||
final formParams = <String, String>{};
|
||||
|
||||
queryParams.addAll(_queryParams('', 'lat', lat));
|
||||
queryParams.addAll(_queryParams('', 'lon', lon));
|
||||
|
||||
const contentTypes = <String>[];
|
||||
|
||||
|
||||
return apiClient.invokeAPI(
|
||||
path,
|
||||
'GET',
|
||||
queryParams,
|
||||
postBody,
|
||||
headerParams,
|
||||
formParams,
|
||||
contentTypes.isEmpty ? null : contentTypes.first,
|
||||
);
|
||||
}
|
||||
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [double] lat (required):
|
||||
///
|
||||
/// * [double] lon (required):
|
||||
Future<List<MapReverseGeocodeResponseDto>?> reverseGeocode(double lat, double lon,) async {
|
||||
final response = await reverseGeocodeWithHttpInfo(lat, lon,);
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
}
|
||||
// When a remote server returns no body with a status of 204, we shall not decode it.
|
||||
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
|
||||
// FormatException when trying to decode an empty string.
|
||||
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
|
||||
final responseBody = await _decodeBodyBytes(response);
|
||||
return (await apiClient.deserializeAsync(responseBody, 'List<MapReverseGeocodeResponseDto>') as List)
|
||||
.cast<MapReverseGeocodeResponseDto>()
|
||||
.toList(growable: false);
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
2
mobile/openapi/lib/api_client.dart
generated
2
mobile/openapi/lib/api_client.dart
generated
@ -362,6 +362,8 @@ class ApiClient {
|
||||
return LogoutResponseDto.fromJson(value);
|
||||
case 'MapMarkerResponseDto':
|
||||
return MapMarkerResponseDto.fromJson(value);
|
||||
case 'MapReverseGeocodeResponseDto':
|
||||
return MapReverseGeocodeResponseDto.fromJson(value);
|
||||
case 'MapTheme':
|
||||
return MapThemeTypeTransformer().decode(value);
|
||||
case 'MemoryCreateDto':
|
||||
|
126
mobile/openapi/lib/model/map_reverse_geocode_response_dto.dart
generated
Normal file
126
mobile/openapi/lib/model/map_reverse_geocode_response_dto.dart
generated
Normal file
@ -0,0 +1,126 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.18
|
||||
|
||||
// ignore_for_file: unused_element, unused_import
|
||||
// ignore_for_file: always_put_required_named_parameters_first
|
||||
// ignore_for_file: constant_identifier_names
|
||||
// ignore_for_file: lines_longer_than_80_chars
|
||||
|
||||
part of openapi.api;
|
||||
|
||||
class MapReverseGeocodeResponseDto {
|
||||
/// Returns a new [MapReverseGeocodeResponseDto] instance.
|
||||
MapReverseGeocodeResponseDto({
|
||||
required this.city,
|
||||
required this.country,
|
||||
required this.state,
|
||||
});
|
||||
|
||||
String? city;
|
||||
|
||||
String? country;
|
||||
|
||||
String? state;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is MapReverseGeocodeResponseDto &&
|
||||
other.city == city &&
|
||||
other.country == country &&
|
||||
other.state == state;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(city == null ? 0 : city!.hashCode) +
|
||||
(country == null ? 0 : country!.hashCode) +
|
||||
(state == null ? 0 : state!.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'MapReverseGeocodeResponseDto[city=$city, country=$country, state=$state]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
if (this.city != null) {
|
||||
json[r'city'] = this.city;
|
||||
} else {
|
||||
// json[r'city'] = null;
|
||||
}
|
||||
if (this.country != null) {
|
||||
json[r'country'] = this.country;
|
||||
} else {
|
||||
// json[r'country'] = null;
|
||||
}
|
||||
if (this.state != null) {
|
||||
json[r'state'] = this.state;
|
||||
} else {
|
||||
// json[r'state'] = null;
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [MapReverseGeocodeResponseDto] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static MapReverseGeocodeResponseDto? fromJson(dynamic value) {
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return MapReverseGeocodeResponseDto(
|
||||
city: mapValueOfType<String>(json, r'city'),
|
||||
country: mapValueOfType<String>(json, r'country'),
|
||||
state: mapValueOfType<String>(json, r'state'),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<MapReverseGeocodeResponseDto> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <MapReverseGeocodeResponseDto>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = MapReverseGeocodeResponseDto.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, MapReverseGeocodeResponseDto> mapFromJson(dynamic json) {
|
||||
final map = <String, MapReverseGeocodeResponseDto>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = MapReverseGeocodeResponseDto.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of MapReverseGeocodeResponseDto-objects as value to a dart map
|
||||
static Map<String, List<MapReverseGeocodeResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<MapReverseGeocodeResponseDto>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = MapReverseGeocodeResponseDto.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
'city',
|
||||
'country',
|
||||
'state',
|
||||
};
|
||||
}
|
||||
|
@ -3109,6 +3109,60 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/map/reverse-geocode": {
|
||||
"get": {
|
||||
"operationId": "reverseGeocode",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "lat",
|
||||
"required": true,
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"format": "double",
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "lon",
|
||||
"required": true,
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"format": "double",
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/MapReverseGeocodeResponseDto"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"bearer": []
|
||||
},
|
||||
{
|
||||
"cookie": []
|
||||
},
|
||||
{
|
||||
"api_key": []
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"Map"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/map/style.json": {
|
||||
"get": {
|
||||
"operationId": "getMapStyle",
|
||||
@ -9128,6 +9182,28 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"MapReverseGeocodeResponseDto": {
|
||||
"properties": {
|
||||
"city": {
|
||||
"nullable": true,
|
||||
"type": "string"
|
||||
},
|
||||
"country": {
|
||||
"nullable": true,
|
||||
"type": "string"
|
||||
},
|
||||
"state": {
|
||||
"nullable": true,
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"city",
|
||||
"country",
|
||||
"state"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"MapTheme": {
|
||||
"enum": [
|
||||
"light",
|
||||
|
@ -554,6 +554,11 @@ export type MapMarkerResponseDto = {
|
||||
lon: number;
|
||||
state: string | null;
|
||||
};
|
||||
export type MapReverseGeocodeResponseDto = {
|
||||
city: string | null;
|
||||
country: string | null;
|
||||
state: string | null;
|
||||
};
|
||||
export type OnThisDayDto = {
|
||||
year: number;
|
||||
};
|
||||
@ -1991,6 +1996,20 @@ export function getMapMarkers({ fileCreatedAfter, fileCreatedBefore, isArchived,
|
||||
...opts
|
||||
}));
|
||||
}
|
||||
export function reverseGeocode({ lat, lon }: {
|
||||
lat: number;
|
||||
lon: number;
|
||||
}, opts?: Oazapfts.RequestOpts) {
|
||||
return oazapfts.ok(oazapfts.fetchJson<{
|
||||
status: 200;
|
||||
data: MapReverseGeocodeResponseDto[];
|
||||
}>(`/map/reverse-geocode${QS.query(QS.explode({
|
||||
lat,
|
||||
lon
|
||||
}))}`, {
|
||||
...opts
|
||||
}));
|
||||
}
|
||||
export function getMapStyle({ key, theme }: {
|
||||
key?: string;
|
||||
theme: MapTheme;
|
||||
|
@ -1,7 +1,12 @@
|
||||
import { Controller, Get, Query } from '@nestjs/common';
|
||||
import { Controller, Get, HttpCode, HttpStatus, Query } from '@nestjs/common';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { AuthDto } from 'src/dtos/auth.dto';
|
||||
import { MapMarkerDto, MapMarkerResponseDto } from 'src/dtos/search.dto';
|
||||
import {
|
||||
MapMarkerDto,
|
||||
MapMarkerResponseDto,
|
||||
MapReverseGeocodeDto,
|
||||
MapReverseGeocodeResponseDto,
|
||||
} from 'src/dtos/map.dto';
|
||||
import { MapThemeDto } from 'src/dtos/system-config.dto';
|
||||
import { Auth, Authenticated } from 'src/middleware/auth.guard';
|
||||
import { MapService } from 'src/services/map.service';
|
||||
@ -22,4 +27,11 @@ export class MapController {
|
||||
getMapStyle(@Query() dto: MapThemeDto) {
|
||||
return this.service.getMapStyle(dto.theme);
|
||||
}
|
||||
|
||||
@Authenticated()
|
||||
@Get('reverse-geocode')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
reverseGeocode(@Query() dto: MapReverseGeocodeDto): Promise<MapReverseGeocodeResponseDto[]> {
|
||||
return this.service.reverseGeocode(dto);
|
||||
}
|
||||
}
|
||||
|
67
server/src/dtos/map.dto.ts
Normal file
67
server/src/dtos/map.dto.ts
Normal file
@ -0,0 +1,67 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { Type } from 'class-transformer';
|
||||
import { IsLatitude, IsLongitude } from 'class-validator';
|
||||
import { ValidateBoolean, ValidateDate } from 'src/validation';
|
||||
|
||||
export class MapReverseGeocodeDto {
|
||||
@ApiProperty({ format: 'double' })
|
||||
@Type(() => Number)
|
||||
@IsLatitude({ message: ({ property }) => `${property} must be a number between -90 and 90` })
|
||||
lat!: number;
|
||||
|
||||
@ApiProperty({ format: 'double' })
|
||||
@Type(() => Number)
|
||||
@IsLongitude({ message: ({ property }) => `${property} must be a number between -180 and 180` })
|
||||
lon!: number;
|
||||
}
|
||||
|
||||
export class MapReverseGeocodeResponseDto {
|
||||
@ApiProperty()
|
||||
city!: string | null;
|
||||
|
||||
@ApiProperty()
|
||||
state!: string | null;
|
||||
|
||||
@ApiProperty()
|
||||
country!: string | null;
|
||||
}
|
||||
|
||||
export class MapMarkerDto {
|
||||
@ValidateBoolean({ optional: true })
|
||||
isArchived?: boolean;
|
||||
|
||||
@ValidateBoolean({ optional: true })
|
||||
isFavorite?: boolean;
|
||||
|
||||
@ValidateDate({ optional: true })
|
||||
fileCreatedAfter?: Date;
|
||||
|
||||
@ValidateDate({ optional: true })
|
||||
fileCreatedBefore?: Date;
|
||||
|
||||
@ValidateBoolean({ optional: true })
|
||||
withPartners?: boolean;
|
||||
|
||||
@ValidateBoolean({ optional: true })
|
||||
withSharedAlbums?: boolean;
|
||||
}
|
||||
|
||||
export class MapMarkerResponseDto {
|
||||
@ApiProperty()
|
||||
id!: string;
|
||||
|
||||
@ApiProperty({ format: 'double' })
|
||||
lat!: number;
|
||||
|
||||
@ApiProperty({ format: 'double' })
|
||||
lon!: number;
|
||||
|
||||
@ApiProperty()
|
||||
city!: string | null;
|
||||
|
||||
@ApiProperty()
|
||||
state!: string | null;
|
||||
|
||||
@ApiProperty()
|
||||
country!: string | null;
|
||||
}
|
@ -289,26 +289,6 @@ export class SearchExploreResponseDto {
|
||||
items!: SearchExploreItem[];
|
||||
}
|
||||
|
||||
export class MapMarkerDto {
|
||||
@ValidateBoolean({ optional: true })
|
||||
isArchived?: boolean;
|
||||
|
||||
@ValidateBoolean({ optional: true })
|
||||
isFavorite?: boolean;
|
||||
|
||||
@ValidateDate({ optional: true })
|
||||
fileCreatedAfter?: Date;
|
||||
|
||||
@ValidateDate({ optional: true })
|
||||
fileCreatedBefore?: Date;
|
||||
|
||||
@ValidateBoolean({ optional: true })
|
||||
withPartners?: boolean;
|
||||
|
||||
@ValidateBoolean({ optional: true })
|
||||
withSharedAlbums?: boolean;
|
||||
}
|
||||
|
||||
export class MemoryLaneDto {
|
||||
@IsInt()
|
||||
@Type(() => Number)
|
||||
@ -324,22 +304,3 @@ export class MemoryLaneDto {
|
||||
@ApiProperty({ type: 'integer' })
|
||||
month!: number;
|
||||
}
|
||||
export class MapMarkerResponseDto {
|
||||
@ApiProperty()
|
||||
id!: string;
|
||||
|
||||
@ApiProperty({ format: 'double' })
|
||||
lat!: number;
|
||||
|
||||
@ApiProperty({ format: 'double' })
|
||||
lon!: number;
|
||||
|
||||
@ApiProperty()
|
||||
city!: string | null;
|
||||
|
||||
@ApiProperty()
|
||||
state!: string | null;
|
||||
|
||||
@ApiProperty()
|
||||
country!: string | null;
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Inject } from '@nestjs/common';
|
||||
import { SystemConfigCore } from 'src/cores/system-config.core';
|
||||
import { AuthDto } from 'src/dtos/auth.dto';
|
||||
import { MapMarkerDto, MapMarkerResponseDto } from 'src/dtos/search.dto';
|
||||
import { MapMarkerDto, MapMarkerResponseDto, MapReverseGeocodeDto } from 'src/dtos/map.dto';
|
||||
import { IAlbumRepository } from 'src/interfaces/album.interface';
|
||||
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
||||
import { IMapRepository } from 'src/interfaces/map.interface';
|
||||
@ -53,4 +53,11 @@ export class MapService {
|
||||
|
||||
return JSON.parse(await this.systemMetadataRepository.readFile(`./resources/style-${theme}.json`));
|
||||
}
|
||||
|
||||
async reverseGeocode(dto: MapReverseGeocodeDto) {
|
||||
const { lat: latitude, lon: longitude } = dto;
|
||||
// eventually this should probably return an array of results
|
||||
const result = await this.mapRepository.reverseGeocode({ latitude, longitude });
|
||||
return result ? [result] : [];
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user