forked from microsoft/TypeAgent
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added nearby location lookup and reverse geocode lookup for images. (m…
…icrosoft#436) Now when adding an image to the system we do a reverse geocode lookup we also perform a nearby POI search.
- Loading branch information
Showing
12 changed files
with
386 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,209 @@ | ||
// Copyright (c) Microsoft Corporation. | ||
// Licensed under the MIT License. | ||
|
||
import { | ||
AuthTokenProvider, | ||
AzureTokenScopes, | ||
createAzureTokenProvider, | ||
getEnvSetting, | ||
} from "aiclient"; | ||
import { StringArrayTag, TypedTag, XmpTag } from "exifreader"; | ||
import { ApiSettings, EnvVars } from "../../aiclient/dist/openai.js"; | ||
import { env } from "process"; | ||
import { | ||
GeoJsonFeature, | ||
GeoJsonFeatureCollection, | ||
SearchAddressResult, | ||
SearchAddressResultItem, | ||
} from "@azure/maps-search"; | ||
import { AddressOutput } from "@azure-rest/maps-search"; | ||
|
||
/** | ||
* Point of interest | ||
*/ | ||
export type PointOfInterest = { | ||
name?: String | undefined; | ||
categories?: String[] | undefined; | ||
freeFormAddress?: String | undefined; | ||
position?: LatLong | undefined; | ||
}; | ||
|
||
/** | ||
* Reverse geocode lookup | ||
*/ | ||
export type ReverseGeocodeAddressLookup = { | ||
address?: AddressOutput | undefined; | ||
confidence?: "High" | "Medium" | "Low" | undefined; | ||
type: any; | ||
}; | ||
|
||
/** | ||
* Latitude, longitude coordinates | ||
*/ | ||
export type LatLong = { | ||
latitude: Number | String | undefined; | ||
longitude: Number | String | undefined; | ||
}; | ||
|
||
/** | ||
* Helper method to convert EXIF tags to Latlong type | ||
* @param exifLat - The exif latitude. | ||
* @param exifLatRef - The exif latitude reference. | ||
* @param exifLong - The exif longitude | ||
* @param exifLongRef - The exif longitude reference. | ||
* @returns The LatLong represented by the EXIF tag or undefined when data is incomplete | ||
*/ | ||
export function exifGPSTagToLatLong( | ||
exifLat: | ||
| XmpTag | ||
| TypedTag<[[number, number], [number, number], [number, number]]> | ||
| undefined, | ||
exifLatRef: XmpTag | StringArrayTag | undefined, | ||
exifLong: | ||
| XmpTag | ||
| TypedTag<[[number, number], [number, number], [number, number]]> | ||
| undefined, | ||
exifLongRef: XmpTag | StringArrayTag | undefined, | ||
): LatLong | undefined { | ||
if ( | ||
exifLat !== undefined && | ||
exifLong !== undefined && | ||
exifLatRef !== undefined && | ||
exifLongRef !== undefined | ||
) { | ||
return { | ||
latitude: | ||
exifLatRef.value == "S" | ||
? parseFloat("-" + exifLat.description) | ||
: parseFloat(exifLat.description), | ||
longitude: | ||
exifLongRef.value == "W" | ||
? parseFloat("-" + exifLong.description) | ||
: parseFloat(exifLong.description), | ||
}; | ||
} | ||
|
||
return undefined; | ||
} | ||
|
||
/** | ||
* Gets the nearby POIs for the supplied coordinate and search radius | ||
* @param position - the position at which to do a nearby search | ||
* @param settings - the API settings containing the endpoint to call | ||
* @param radius - the search radius | ||
* @returns A list of summarized nearby POIs | ||
*/ | ||
export async function findNearbyPointsOfInterest( | ||
position: LatLong | undefined, | ||
settings: ApiSettings, | ||
radius: Number = 10, | ||
): Promise<PointOfInterest[]> { | ||
if (position === undefined) { | ||
return []; | ||
} | ||
|
||
const tokenProvider: AuthTokenProvider = createAzureTokenProvider( | ||
AzureTokenScopes.AzureMaps, | ||
); | ||
const tokenResult = await tokenProvider.getAccessToken(); | ||
if (!tokenResult.success) { | ||
return []; | ||
} | ||
|
||
try { | ||
//let fuzzySearch = `${getEnvSetting(env, EnvVars.AZURE_MAPS_ENDPOINT)}search/fuzzy/json?api-version=1.0&query={lat,long}` | ||
//let poi = `${getEnvSetting(env, EnvVars.AZURE_MAPS_ENDPOINT)}search/poi/{format}?api-version=1.0&lat={LAT}&lon={LON}` | ||
const nearby = `${getEnvSetting(env, EnvVars.AZURE_MAPS_ENDPOINT)}search/nearby/json?api-version=1.0&lat=${position.latitude}&lon=${position.longitude}&radius=${radius}`; | ||
const options: RequestInit = { | ||
method: "GET", | ||
headers: new Headers({ | ||
Authorization: `Bearer ${tokenResult.data}`, | ||
"x-ms-client-id": `${getEnvSetting(env, EnvVars.AZURE_MAPS_CLIENTID)}`, | ||
}), | ||
}; | ||
|
||
// get the result | ||
const response = await fetch(nearby, options); | ||
let responseBody = await response.json(); | ||
|
||
// summarize results | ||
const results: SearchAddressResult = | ||
responseBody as SearchAddressResult; | ||
const retVal: PointOfInterest[] = []; | ||
results.results.map((result: SearchAddressResultItem) => { | ||
if (result.type == "POI") { | ||
retVal.push({ | ||
name: result.pointOfInterest?.name, | ||
categories: result.pointOfInterest?.categories, | ||
freeFormAddress: result.address.freeformAddress, | ||
position: { | ||
latitude: result.position[0], | ||
longitude: result.position[1], | ||
}, | ||
}); | ||
} else { | ||
// TODO: handle more result types | ||
throw new Error("Unknown nearby search result!"); | ||
} | ||
}); | ||
|
||
// TODO: if there are no POI, can we just send back the address? | ||
// Do we increase POI search radius until we find something in some predefined maximum area? | ||
return retVal; | ||
} catch (e) { | ||
console.warn(`Error performing nearby POI lookup: ${e}`); | ||
return []; | ||
} | ||
} | ||
|
||
export async function reverseGeocode( | ||
position: LatLong | undefined, | ||
settings: ApiSettings, | ||
): Promise<ReverseGeocodeAddressLookup[]> { | ||
if (position === undefined) { | ||
return []; | ||
} | ||
|
||
const tokenProvider: AuthTokenProvider = createAzureTokenProvider( | ||
AzureTokenScopes.AzureMaps, | ||
); | ||
const tokenResult = await tokenProvider.getAccessToken(); | ||
if (!tokenResult.success) { | ||
return []; | ||
} | ||
|
||
try { | ||
let reverseGeocode = `${getEnvSetting(env, EnvVars.AZURE_MAPS_ENDPOINT)}reverseGeocode?api-version=2023-06-01&coordinates=${position.longitude},${position.latitude}`; | ||
|
||
const options: RequestInit = { | ||
method: "GET", | ||
headers: new Headers({ | ||
Authorization: `Bearer ${tokenResult.data}`, | ||
"x-ms-client-id": `${getEnvSetting(env, EnvVars.AZURE_MAPS_CLIENTID)}`, | ||
}), | ||
}; | ||
|
||
// get the result | ||
const response = await fetch(reverseGeocode, options); | ||
let responseBody = await response.json(); | ||
|
||
// summarize results | ||
const results: GeoJsonFeatureCollection = | ||
responseBody as GeoJsonFeatureCollection; | ||
const retVal: ReverseGeocodeAddressLookup[] = []; | ||
results.features?.map((result: GeoJsonFeature) => { | ||
if (result.properties !== undefined) { | ||
retVal.push({ | ||
address: result.properties.address, | ||
confidence: result.properties.confidence, | ||
type: result.properties.type, | ||
}); | ||
} | ||
}); | ||
|
||
return retVal; | ||
} catch (e) { | ||
console.warn(`Unable to perform reverse geocode lookup: ${e}`); | ||
return []; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
// Copyright (c) Microsoft Corporation. | ||
// Licensed under the MIT License. | ||
|
||
import { XmpTag } from "exifreader"; | ||
import { exifGPSTagToLatLong, LatLong } from "../src/location.js"; | ||
|
||
describe("Location Tests", () => { | ||
it("EXIF LatLong to LatLong", () => { | ||
const lat: XmpTag = { | ||
value: "47.6204", | ||
description: "47.6204", | ||
attributes: {}, | ||
}; | ||
const long: XmpTag = { | ||
value: "122.3491", | ||
description: "122.3491", | ||
attributes: {}, | ||
}; | ||
const latRef: XmpTag = { | ||
value: "N", | ||
description: "North Latitude", | ||
attributes: {}, | ||
}; | ||
const longRef: XmpTag = { | ||
value: "W", | ||
description: "West Longitude", | ||
attributes: {}, | ||
}; | ||
|
||
const ll: LatLong = exifGPSTagToLatLong(lat, latRef, long, longRef)!; | ||
expect(ll.latitude == "47.6204"); | ||
expect(ll.longitude == "-122.3491"); | ||
|
||
const llu: LatLong | undefined = exifGPSTagToLatLong( | ||
lat, | ||
latRef, | ||
undefined, | ||
longRef, | ||
); | ||
expect(llu === undefined); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.