Skip to content

Conversation

@bajtos
Copy link
Member

@bajtos bajtos commented May 21, 2018

This pull request improves our Todo example to demonstrate how to use Services in practice. To do so, we leverage US Census Geocoder (API docs) to add location-based reminders to our Todo items.

Based on the outcome of this work, I'll update our Services docs with best practices on testing, see
#1311, and also update Todo tutorial to match the new Todo example implementation. These documentation updates are out of scope of this pull request though!

Checklist

  • npm test passes on your machine
  • New tests added or existing tests modified to cover all changes
  • Code conforms with the style guide
  • API Documentation in code was updated
  • Documentation in /docs/site was updated
  • Affected artifact templates in packages/cli were updated
  • Affected example projects in examples/* were updated

@bajtos bajtos self-assigned this May 21, 2018
@raymondfeng
Copy link
Contributor

Great work. The only concern I have is that it's not reliable to test Google GeoService due to rate limiting unless we allows optional credentials for Google map. Please note that travis can encrypt env vars - see https://docs.travis-ci.com/user/environment-variables/.

@bajtos
Copy link
Member Author

bajtos commented May 22, 2018

The only concern I have is that it's not reliable to test Google GeoService due to rate limiting unless we allows optional credentials for Google map. Please note that travis can encrypt env vars - see https://docs.travis-ci.com/user/environment-variables/.

It's getting even worse. See https://cloud.google.com/maps-platform/user-guide/

Beginning on June 11, 2018, you’ll need to enable billing with a credit card and have a valid API key for all projects.

I'll look for other alternatives providing a free geocoding API.

@bajtos
Copy link
Member Author

bajtos commented May 22, 2018

I spent more than an hour researching different options. Most hosted solutions are paid or have too strictly rate limit (e.g. OpenStreetMap's Nominatim allows only one request per second). The only reasonable and free solution I found is US Census Geocoder (API docs).

I am going to rework the Todo example to use this service instead of Google Maps.

@bajtos
Copy link
Member Author

bajtos commented May 22, 2018

@bajtos
Copy link
Member Author

bajtos commented Jun 4, 2018

I think we need to put this on hold until @virkt25 work on datasource support in CLI and bootstrapper are landed - see #1385. Otherwise we will end up with a bunch of conflicts.

@bajtos bajtos force-pushed the feat/todo-with-location branch from 996f447 to 2186e8f Compare June 14, 2018 13:23
@bajtos bajtos changed the title WIP: add Geo Service to examples/todo Add GeoCoder Service to examples/todo Jun 14, 2018
@bajtos
Copy link
Member Author

bajtos commented Jun 14, 2018

Rebased on top of the latest master and prepared for review.

@raymondfeng @strongloop/sq-lb-apex PTAL.

@bajtos bajtos added this to the June Milestone milestone Jun 14, 2018
) {
dsConfig = Object.assign({}, dsConfig, {
// A workaround for the current design flaw where inside our monorepo,
// packages/repository-proxy/node_modules/loopback-datasource-juggler
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did you mean packages/repository/node_modules here instead of packages/repository-proxy/node_modules?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch! I meant service-proxy instead of repository-proxy. Will fix.

expect(result).to.containDeep(todo);
});

it('creates an address-based reminder', async () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would adding a test for retrieval of a todo with remindAtGeo and remindAtAddress set add value to our test suite?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so - we already have a test to verify retrieval of a todo. Unless you see reasons why retrieval of a todo with remindAt* fields may work differently from retrieval of todos without those fields set?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's to see that we can retrieve them as well as create them with those fields, but I don't think it's needed tbh.

@bajtos bajtos force-pushed the feat/todo-with-location branch from 2186e8f to e98646e Compare June 15, 2018 06:27
Copy link
Contributor

@virkt25 virkt25 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work 👏 ! Just have a few minor questions. Overall looks great.

if (todo.remindAtAddress) {
// TODO(bajtos) handle "address not found"
const geo = await this.geoService.geocode(todo.remindAtAddress);
todo.remindAtGeo = `${geo[0].y},${geo[0].x}`;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are geo coordinates typically stored as y, x and not the standard x, y?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a great question! For reference, y is latitude and x is longitude.

As far as I can tell from searching around, there is no wide consensus on the right order :(

In StrongLoop, we were always using lat,lng order to be compatible with Google Maps API, since that's the API most people seem to be using and be familiar with these days .

https://stackoverflow.com/q/7309121/69868

  • EPSG:4326 specifically states that the coordinate order should be latitude, longitude. Many software packages still use longitude, latitude ordering. This situation has wreaked unimaginable havoc on project deadlines and programmer sanity.
  • The prefered order is by convention latitude, longitude. This was presumably standardized by the International Maritime Organization as reported here. Google also uses this order in its Maps and Earth. I remember this order by thinking of alphabetic order of latitude, longitude.

BUT:

The correct order is longitude, latitude, in virtually all professional GIS applications, as it is in conventional math (ie, f(x ,y, z)). The GeoJSON standard is fairly typical and succinct:

The order of elements must follow x, y, z order
(easting, northing, altitude for coordinates in a
projected coordinate reference system, or longitude,
latitude, altitude for coordinates in a geographic
coordinate reference system).

The same is true of the primary Open Geospatial Consortium standards (WKT and WKB, and extensions like EWKB). Likewise Google may output the order in Lat/Lon to make it more familiar to users who grew up with that custom (ie from navigation standards like IMO, rather than computational ones.) But the KML standard itself is like virtually all other GIS systems:

https://gis.stackexchange.com/q/7379

We are used to seeing latitude and longitude marked on paper maps, tempting us to think of these as planar (Cartesian) coordinates. However, they are not; the paper (planar) map is a projection of spherical coordinates, and spherical coordinates are generally written as radius, inclination, and azimuth (at least in physics.) In fact, the order radius, inclination, azimuth is codified in ISO 31-11. Geographers don't need the radius (or to the extent they do, they use elevation/altitude, which is the deviation from the nominal radius of the earth), so we just have inclination (latitude) and azimuth (longitude). From this perspective, latitude/longitude is perfectly rational.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a comment explaining the order and pointing to those stackexchange/stackoverflow pages.

To be honest, I don't really mind which order we use. If you think we should switch to lng,lat, then please let me know and I'll make that change in a follow-up pull request. (You can also send that PR yourself if you like.)

Copy link
Contributor

@virkt25 virkt25 Jun 18, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The convention I'm familiar with / expect myself is lat, long but it was the x,y that threw me ... but we don't exactly have control over that as it's coming from an external service. I'm ok with the changes as they currently stand. Thanks for the explanation and links!

before(async () => {
await app.boot();
let cachingProxy: HttpCachingProxy;
before(async () => (cachingProxy = await givenCachingProxy()));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason for the async?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I need to store the result of asynchronous givenCachingProxy in cachingProxy variable.

await app.boot();

/**
* Override DataSource to not write to file for testing. Since we aren't
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While you're here can you please update this comment -- Override default config for DataSource for testing so we don't write tests to file when using the memory connector. - Rest isn't relevant as it's being handled by Booter. My mistake for not updating this comment.

return config;
}

export {HttpCachingProxy};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this export necessary?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not strictly necessary, but it simplifies imports in test files (application.acceptance.ts and geocoder.service.integration.ts). With the re-export in place, I can do the following:

import {HttpCachingProxy, givenCachingProxy} from '../../helpers';

const GEO_CODER_CONFIG = require('../src/datasources/geocoder.datasource.json');

export function getProxiedGeoCoderConfig(proxy: HttpCachingProxy) {
const config = Object.assign({}, GEO_CODER_CONFIG);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason to not do this:

const config = Object.assign({}, GEO_CODER_CONFIG, {options: {proxy: proxy.url, tunnel: false}})

In this case, the return can be in the same line as well.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your code snippet won't work because Object.assign performs a shallow merge (only top-level properties are merged) andGEO_CODER_CONFIG already contains options:

{
  // ...
  "options": {
    "headers": {
      "accept": "application/json",
      "content-type": "application/json"
    }
  },
  // ...
}

To avoid future confusion, I'll rework this code to use lodash.merge.

// loopback dependencies
export {inject, Context} from '@loopback/context';
export {Server} from './server';
export * from '@loopback/context';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason for the switch to *?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typical applications need more than just inject and Context. When I revert this change, I see the following compilation errors:

examples/todo/src/application.ts(6,28): error TS2305: 
  Module '"packages/core/index"' has no exported member 'Provider'.

examples/todo/src/application.ts(6,38): error TS2305: 
  Module '"packages/core/index"' has no exported member 'Constructor'.

examples/todo/src/services/geocoder.service.ts(7,17): error TS2305: 
  Module '"packages/core/index"' has no exported member 'Provider'.

@bajtos bajtos force-pushed the feat/todo-with-location branch from e98646e to 7ceb2c4 Compare June 18, 2018 09:14
@bajtos bajtos merged commit b4a9a9e into master Jun 18, 2018
@bajtos bajtos deleted the feat/todo-with-location branch June 18, 2018 09:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants