fiftyone.geolocation
Version:
Perform reverse geocoding using longitude and latitude to populate postal addresses
234 lines (203 loc) • 7.3 kB
JavaScript
/* *********************************************************************
* This Original Work is copyright of 51 Degrees Mobile Experts Limited.
* Copyright 2025 51 Degrees Mobile Experts Limited, Davidson House,
* Forbury Square, Reading, Berkshire, United Kingdom RG1 3EU.
*
* This Original Work is licensed under the European Union Public Licence
* (EUPL) v.1.2 and is subject to its terms as set out below.
*
* If a copy of the EUPL was not distributed with this file, You can obtain
* one at https://opensource.org/licenses/EUPL-1.2.
*
* The 'Compatible Licences' set out in the Appendix to the EUPL (as may be
* amended by the European Commission) shall be deemed incompatible for
* the purposes of the Work and the provisions of the compatibility
* clause in Article 5 of the EUPL shall not apply.
*
* If using the Work as, or as part of, a network application, by
* including the attribution notice(s) required under Article 5 of the EUPL
* in the end user terms of the application under an appropriate heading,
* such notice(s) shall fulfill the requirements of that article.
* ********************************************************************* */
const path = require('path');
const GeoLocation = require(path.resolve(__dirname, '..'));
const myResourceKey = process.env.RESOURCE_KEY || '!!YOUR_RESOURCE_KEY!!';
const TestLat = '51.4578261';
const TestLon = '-0.975922996290084';
const expectedProperties = {
location: [
'javascript',
'building',
'streetnumber',
'road',
'town',
// For some reason, the value of the 'county' property returned
// by the call to the cloud is always null on the build agent,
// despite working correctly locally.
// After spending the best part of a day trying to resolve this,
// we've decided to exclude the county property from this test
// for the moment.
// 'county',
'region',
'state',
'zipcode',
'country',
'countrycode'
],
location_digitalelement: [
'javascript',
'town',
'county',
'region',
'state',
'zipcode',
'country',
'countrycode'
]
};
// Skip the rest of the examples when async is not available
let isAsync = true;
try {
eval('async () => {}');
} catch (e) {
isAsync = false;
}
if (isAsync) {
// Check that if no evidence is yet available for location
// engine, accessing a valid property will return HasValue=false
// and a correct error message.
test('No evidence error message', done => {
if (myResourceKey === '!!YOUR_RESOURCE_KEY!!') {
throw new Error('No resource key is present!');
}
const pipeline = new GeoLocation.GeoLocationPipelineBuilder({
resourceKey: myResourceKey
}).build();
const flowData = pipeline.createFlowData();
flowData.process().then(function () {
const country = flowData.location.country;
expect(country.hasValue).toBe(false);
// Debug: log the actual message to understand CI failure
console.log('DEBUG - Actual noValueMessage:', JSON.stringify(country.noValueMessage));
expect(country.noValueMessage.indexOf('This property requires ' +
'evidence values from JavaScript running on the client. It ' +
'cannot be populated until a future request is made that ' +
'contains this additional data.') !== -1).toBe(true);
done();
});
});
test('Available Properties 51Degrees', async done => {
if (myResourceKey === '!!YOUR_RESOURCE_KEY!!') {
throw new Error('No resource key is present!');
}
const pipeline = new GeoLocation.GeoLocationPipelineBuilder({
resourceKey: myResourceKey,
locationProvider: 'fiftyonedegrees'
}).build();
const engine = pipeline.flowElements.location;
await testAvailableProperties(done, pipeline, engine);
});
test('Value Types 51Degrees', async done => {
if (myResourceKey === '!!YOUR_RESOURCE_KEY!!') {
throw new Error('No resource key is present!');
}
const pipeline = new GeoLocation.GeoLocationPipelineBuilder({
resourceKey: myResourceKey,
locationProvider: 'fiftyonedegrees'
}).build();
const engine = pipeline.flowElements.location;
await testValueTypes(done, pipeline, engine);
});
} else {
// Skip if async is not available (e.g node 6)
test.skip('Workaround', () => 1);
}
async function testAvailableProperties (done, pipeline, engine) {
const flowData = pipeline.createFlowData();
flowData.evidence.add('query.51D_Pos_latitude', TestLat);
flowData.evidence.add('query.51D_Pos_longitude', TestLon);
await flowData.process();
expectedProperties[engine.dataKey].forEach(key => {
try {
const apv = flowData[engine.dataKey][key];
if (apv === undefined) {
done.fail(new Error(`Aspect property value for ${key} should not be undefined.`));
}
expect(apv).not.toBeNull();
expect(apv).toBeDefined();
if (apv.hasValue === true) {
if (apv.value == null || apv.value === undefined) {
done.fail(new Error(`${key}.value should not be null`));
}
} else {
if (apv.noValueMessage == null || apv.noValueMessage === undefined) {
done.fail(new Error(`${key}.noValueMessage should not be null`));
}
}
} catch (err) {
done.fail(err);
}
});
done();
}
async function testValueTypes (done, pipeline, engine) {
const flowData = pipeline.createFlowData();
flowData.evidence.add('query.51D_Pos_latitude', TestLat);
flowData.evidence.add('query.51D_Pos_longitude', TestLon);
await flowData.process();
Object.keys(engine.properties).forEach(key => {
if (expectedProperties[engine.dataKey].includes(key)) {
const property = engine.properties[key.toLowerCase()];
const expectedType = property.type;
const apv = flowData[engine.dataKey][key];
expect(apv).not.toBeNull();
expect(apv).toBeDefined();
expect(apv.hasValue, 'No value for \'' + key + '\'. (' + apv.noValueMessage + ')').toBe(true);
expect(apv.value).toBe51DType(key, expectedType);
}
});
done();
}
expect.extend({
// Method to validate a given value has the expected type.
toBe51DType (received, name, fodType) {
const valueType = typeof received;
let valid = false;
switch (fodType) {
case 'Boolean':
valid = valueType === 'boolean';
break;
case 'String':
valid = valueType === 'string';
break;
case 'JavaScript':
valid = valueType === 'string';
break;
case 'Int32':
valid = valueType === 'number';
break;
case 'Double':
valid = valueType === 'number';
break;
case 'Array':
valid = valueType === 'object' && Array.isArray(received);
break;
default:
valid = false;
break;
}
if (valid) {
return {
message: () =>
`${name}: expected node type '${valueType}' not to be equivalent to fodType '${fodType}' for value: '${received}'`,
pass: true
};
} else {
return {
message: () =>
`${name}: expected node type '${valueType}' to be equivalent to fodType '${fodType}' for value: '${received}'`,
pass: false
};
}
}
});