Skip to content

Commit

Permalink
ability to input geohash, ujpdate docs/test
Browse files Browse the repository at this point in the history
  • Loading branch information
dskvr committed Feb 5, 2024
1 parent 3cbdccf commit baad1f5
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 24 deletions.
18 changes: 13 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,13 @@ The `inputData` object can contain the following properties, used to generate ge

- `lat` (number): Latitude coordinate. No default value.
- `lon` (number): Longitude coordinate. No default value.
- `geohash` (string): Geohash coordinate. No default value. Will be ignored if lat & lon are also passed as input as they take precedence.
- `city` (string): Name of the city. No default value.
- `country` (string): Name of the country. No default value.
- `regionName` (string): Name of the region or state. No default value.
- `countryCode` (string): `ISO-3166-1:alpha-2` country code. No default value.
- Other properties (any): Additionally, key value pairs will be ignored but do not throw an error.
- `planetName` (string): Name of a planet.

## Options Reference
The `options` object specifies which types of tags to generate.
Expand All @@ -77,10 +79,16 @@ The `options` object specifies which types of tags to generate.
Please note: that these will only have an effect on the output if the input for their corresponding values were set. This is especially true for passthrough values. Some of these passthrough values may be deduped if they are not unique against ISO values.

- `geohash` (boolean): Includes geohash codes from `ngeohash`, with diminishing resolution, based on latitude and longitude. Default: `true`.
- `city` (boolean): Include a tag for the city. Default: `true`.
- `country` (boolean): Include a tag for the country. Default: `true`.
- `region` (boolean): Include a tag for the region. Default:`true`.
- `city` or `cityName` (boolean): Include a tag for the city in response **if available**. Default: `true`.
- `country` (boolean): Include a tag for the `countryCode` and `countryName` in response **if available**. Default: `true`.
- `countryCode` (boolean): Include a tag for the `countryCode` in response **if available**. Default: `true`.
- `countryName` (boolean): Include a tag for the `countryName` in response **if available**. Default: `true`.
- `region` (boolean): Include a tag for the `regionCode` and `regionName` in response **if available**. Default:`true`.
- `regionCode` (boolean): Include a tag for the `regionCode` in response **if available**. Default:`true`.
- `regionName` (boolean): Include a tag for the `regionName` in response **if available**. Default:`true`.
- `gps` (boolean): Include latitude and longitude as a 'dd' tag (de-factor GPS standards) and separate tags for lat and lon with diminishing resolution. Default: `false`.
- `planet` or `planetName` (boolean): Include a tag for the `planetName` in response **if available**. Default: `false`.



## Response Reference
Expand All @@ -104,11 +112,11 @@ Which tags you use depend on use-case. If your concerns are namely geospacial, u
3. **ISO-3166-1 Codes**:
- These tags represent country information derived from the `iso-3166` library and are based on the provided `countryCode` input value. They are not passthrough.
- Examples
- (`isoAsNamespace==false [default]`)
- **`isoAsNamespace==false [default]`**
- Alpha-2 code: `[ 'g', 'HU', 'countryCode' ]`
- Alpha-3 code: `[ 'g', 'HUN', 'countryCode']`
- Numeric code: `[ 'g', '348', 'countryCode' ]`
- (`isoAsNamespace==true`)
- **`isoAsNamespace==true`**
- Alpha-2 code: `[ 'g', 'HU', 'ISO-3166-1' ]`
- Alpha-3 code: `[ 'g', 'HUN', 'ISO-3166-1']`
- Numeric code: `[ 'g', '348', 'ISO-3166-1' ]`
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "nostr-geotags",
"version": "0.3.1",
"version": "0.4.0",
"description": "Give an object of geodata, returns standardized nostr geotags ",
"type": "module",
"main": "dist/index.js",
Expand Down
40 changes: 37 additions & 3 deletions src/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@


import { describe, it, expect, vi} from 'vitest';
import ngeotags, { InputData, Option, filterNonStringTags, generateCountryTagKey, sortTagsByKey, GeoTags, filterOutType, iso31661Namespace, iso31662Namespace, iso31663Namespace } from './index'; // Adjust the import path as needed
import ngeotags, { calculateResolution, InputData, Option, filterNonStringTags, generateCountryTagKey, sortTagsByKey, GeoTags, filterOutType, iso31661Namespace, iso31662Namespace, iso31663Namespace } from './index'; // Adjust the import path as needed

describe('generateTags()', () => {

Expand Down Expand Up @@ -298,7 +298,7 @@ describe('generateTags()', () => {
]));
});

const maxResolution = 10; // This should match the value used in your function
const maxResolution = 9; // This should match the value used in your function

it('handles maximum decimal length', () => {
const input = { lat: 47.12345678901, lon: 19.12345678901 };
Expand Down Expand Up @@ -386,7 +386,6 @@ describe('generateTags()', () => {

it('should handle geohash correctly', () => {
const input: InputData = {
geohash: true,
lat: 47.5636,
lon: 19.0947
};
Expand All @@ -395,6 +394,41 @@ describe('generateTags()', () => {
expect(result.some(tag => tag[0] === 'g')).toBeTruthy();
});


it('should decode geohash when geohash passed via input, either lat or lon are null, and gps is enabled', () => {
const input: InputData = {
geohash: 'u2mwdd8q4'
};

const result = ngeotags(input, { gps: true });
console.log('hash', result)
expect(result.some(tag => tag[0] === 'g')).toBeTruthy();
});

it('should inherit default resolution when one is not set', () => {
var result = calculateResolution(12.3456789876545345455, undefined)
expect(result).toBe(9)
})

it('should ignore geohash when geohash passed via input, both lat and long are set (number) and gps is enabled', () => {
const input: InputData = {
geohash: 'h9xhn7y',
lat: 47.56361246109009,
lon: 19.094688892364502
};

const result = ngeotags(input, { gps: true });
console.log('hash and dd passed', result)
expect(result.some(tag => tag[0] === 'g')).toBeTruthy();
expect(result).toEqual(expect.arrayContaining([
[ 'g', '47.5636', 'lat' ],
[ 'g', '19.0946', 'lon' ],
]));
});




it('should handle ISO-3166-1 correctly with optimistic input', () => {
const input: InputData = {
iso31661: true,
Expand Down
54 changes: 39 additions & 15 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import ngeohash from 'ngeohash';
import ngeohash, { GeographicPoint } from 'ngeohash';
import { iso31661, iso31662, iso31663, ISO31661AssignedEntry, ISO31662Entry, ISO31661Entry } from 'iso-3166';

export interface InputData {
geohash?: string,
lat?: number;
lon?: number;
cityName?: string;
Expand All @@ -13,12 +14,13 @@ export interface InputData {
}

export interface Options {
dedupe?: boolean;
sort?: boolean;

isoAsNamespace?: boolean;
unM49AsNamespace?: boolean;

ddMaxResolution?: number;

iso31661?: boolean,
iso31662?: boolean,
iso31663?: boolean,
Expand Down Expand Up @@ -99,6 +101,8 @@ export type LabelTag = LabelNamespace | Label;
*/
export type GeoTags = Geohash | LabelTag;

const DD_MAX_RES_DEFAULT = 9

/**
* Retrieves updated ISO-3166-3 values based on a given code.
*
Expand Down Expand Up @@ -131,6 +135,26 @@ export const iso31662Namespace = (opts: Options): string => opts.isoAsNamespace

export const iso31663Namespace = (opts: Options): string => opts.isoAsNamespace ? 'ISO-3166-3' : 'countryCode';

/**
* Truncates a number (float) to a specified precision. Generally used for dd (lat and lon) values.
*
* @param {number} num - The float to be shortened.
* @param {number} resolution - How many decimal places.
* @returns {GeoTags[]} An array of generated geo tags.
*
* This function shortens a lat or lon to a specified precision (number of decimal places.)
* Does nothing if whole number.
*/
const truncateToResolution = (num: number, resolution: number): number => {
const multiplier = Math.pow(10, resolution);
return Math.floor(num * multiplier) / multiplier;
};


export const calculateResolution = (input: number, max: number | undefined): number => {
if(!max) max = DD_MAX_RES_DEFAULT
return input % 1 === 0 ? 1 : Math.min(input.toString().split('.')?.[1]?.length, max);
}

/**
* Generates an array of `g` tags based on the input data and options provided.
Expand All @@ -141,24 +165,24 @@ export const iso31663Namespace = (opts: Options): string => opts.isoAsNamespace
*
* This function processes the input data and generates a series of tags based on the options.
* It handles various types of data such as GPS coordinates, ISO-3166 country and region codes,
* city. The generated tags are deduplicated by default, can be changed
* with dedupe option.
* city.
*/
const generateTags = (input: InputData, opts: Options): GeoTags[] => {
const tags: GeoTags[] = [];

// GPS
if (opts.gps && input.lat && input.lon) {
if(opts?.gps && input.geohash && (!input?.lat || !input?.lon)) {
const dd = ngeohash.decode(input.geohash)
console.log('wtf', dd)
input.lat = dd.latitude
input.lon = dd.longitude
}
if (opts?.gps && input.lat && input.lon) {
tags.push(['G', `dd`]);
tags.push(['g', `${input.lat}, ${input.lon}`, 'dd']);

const maxResolution = 10;
const truncateToResolution = (num: number, resolution: number): number => {
const multiplier = Math.pow(10, resolution);
return Math.floor(num * multiplier) / multiplier;
};
const latResolution = input.lat % 1 === 0 ? 1 : Math.min(input.lat.toString().split('.')[1].length, maxResolution);
const lonResolution = input.lon % 1 === 0 ? 1 : Math.min(input.lon.toString().split('.')[1].length, maxResolution);

const latResolution = calculateResolution(input.lat, opts.ddMaxResolution);
const lonResolution = calculateResolution(input.lon, opts.ddMaxResolution);

tags.push(['G', `lat`]);
for (let i = latResolution; i > 0; i--) {
Expand Down Expand Up @@ -263,7 +287,6 @@ const generateTags = (input: InputData, opts: Options): GeoTags[] => {
const namespace = iso31662Namespace(opts)
result = filterOutType(result, namespace);
}
// result = opts?.dedupe === true? dedupe(result): result;
result = opts?.sort === true? sortTagsByKey(result): result;
result = sanitize(result)
return result
Expand Down Expand Up @@ -365,12 +388,13 @@ export default (input: InputData | null, opts?: Options): GeoTags[] => {
if (!(input instanceof Object) || Array.isArray(input) || typeof input!== 'object' || typeof input=== 'function' )
throw new Error('Input must be an object');
opts = {
dedupe: true,
sort: false,

isoAsNamespace: false,
unM49AsNamespace: true,

ddMaxResolution: DD_MAX_RES_DEFAULT,

iso31661: true,
iso31662: false,
iso31663: false,
Expand Down

0 comments on commit baad1f5

Please sign in to comment.