Project: Geography Datatype

Discussions about extensions and frameworks

Project: Geography Datatype

Postby ebkwall » Mon Apr 11, 2016 9:07 am

I discovered this application last week and was amazed at the capabilities. As a programmer, ND1 is especially elegant in its ability to ingest external code and present it as native functionality. I found it appealing more for this extensibility than its powerful math capabilities (which are substantial to be sure). With that in mind, I decided to set out to create a Custom Datatype as scoped out below. I came up with these requirements based on my experience with GIS and the few days I had looked at ND1. The requirements may be fluid throughout the Project as my knowledge of ND1, Morphengine and Javascript (I am only fair at Javascript as all my experience for the last 20+ years has been C, C++, VB, VB.Net, and for the last decade or more C#) increases. It is small in scope but exercises several extensibility features.


Project: Create a Custom Datatype for Geography

Instantiation:
Can be created via single string or number pairs.
    eg. 36.532N 154.34W Enter - or 36.532, -154.34 tap ->point
Can set Format for display in String and HTML (Settable in dialog window(s) maybe?)
    eg. Decimal Degrees, Degrees Decimal Minutes, Degrees Minutes Seconds
Internal Types:
Can create Lines from multiple Points
    eg. Enter two points and tap ->line
    Optional - override the "+" key to add the two points on the Stack
Can create Rectangle from two "corner" Points
    eg. Enter two points and tap ->rect
Can create Polygon from a Line by Closing it - add first Point to the Line to Create a Polygon
    eg. From Line object tap ->poly
    Optional - Instead of adding a Point to the Line, set a member var - GeoType to Polygon
For Lines, Rectangles, and Polygons:
    Keep it simple for now and require entries do not span the International Dateline.
    Optional - handle International Dateline for Lines, Rectangles, and Polygons

Functions:
Can find distance between Points as a function with two Points on the Stack which will NOT create a Line.
    eg. Enter two points and tap ->dist
Line shall have Distance as the sum of distance of all points in the Line
When Point is added to a Line, the Stack should show distance from last point and total distance.
Can set Units for distance (Would be cool to integrate the built in Units somehow)

Rectangle can be "set" as Map Window which will be global (or folder?) variable like eq for Plot
Optional Rectangle (Window) and Polygon will have area
Optional set Units for Area to apply to Rectangle (Window) and Polygon

Graphic Display:
Can display Points, Lines, and Polygon in Display window retrieving map from a web resource.
    eg. Google Maps or ESRI
Optional ability to tap or "draw" or points, lines, rectangles and polygons on the map
Optional ability to push data Points onto the Stack similar to getting points from graphs.
Optional ability to push data Line, Rectangles, and Polygons onto the Stack similar to getting points from graphs.



I would like to make incremental code posts and, anyone, please feel free to jump in if you see me straying in any way from these technologies that are new to me.

Thanks much,
Brian
ebkwall
 
Posts: 22
Joined: Tue Apr 05, 2016 2:29 pm

Re: Project: Geography Datatype

Postby ebkwall » Fri Apr 15, 2016 9:35 pm

Well I have a Point and Line Datatype forming up. The code is raw and I am sure not great javascript code.
Oliver, I wanted to get you something to look at and hopefully provide some feedback on the direction I am headed and maybe a tip or two? I wanted to make use of instance functions on the datatype objects but have not figured that out yet.
Also, I have not added the bullet proofing such as type checking and such.

So far:
A Point can be created from a string in the format decimal degrees with direction indicators.
Example: 36.5781N 167.1547W

A Point can also be created from putting two decimal degree numbers on the stack without direction indicators.
Example:
36.5781
-167.1547
or comma separated, then new Point.instance(x,y); - This is where I think i need to look at that factory pattern.
Currently I have that function assigned to a var (soft key) "enter" = function(x, y){ return new Point.instance(x,y);

Display format can be changed by setting Point.format to "dd" or "dms". Setting these to soft keys works well too.

The override for eval is simply a placeholder - smoke and mirrors for the moment.


The Line datatype does not have a string representation. Put two Points on the stack and call:
soft key var "line" = function(x,y){ return new Line.instance(x,y); }
And a Line is returned represented by the Distance between the last two Points and the Total Distance (upon creation last = total)

There is a "+" override for Points which will do the create and return a Line.

You can add Points to a Line and the Last Distance will update as well as the Total Distance.
There is a "+" override for Line adding a Point which will perform this action as well.

Line.units are currently set to kilometers and is not yet configurable. I hope to integrate it somehow with the built in Units. If that can't be done I will add conversion functions.


I had lots of comments so I could keep track of what was going on but the ND1 editor did not like them. Since I have been doing all this on the phone I did not add comments back in yet.

Distance calculation credit goes to http://www.movable-type.co.uk/scripts/latlong.html

Code: Select all
/* Point type definition */
var Point = {
   type: "point",
   format: "dms",
   isLoaded: true,
   toString: function (obj) {
      return obj.lat + ", " + obj.lon;
   },
   toHTML: function (obj) {
      if (Point.format == "dd")
         return Point.toString(obj);
      var typeString = Point.type;
      var stringForType = Point.ConvertDDToDMS(obj.lat, false)
         + " " + Point.ConvertDDToDMS(obj.lon, true);
      return (stringForType + calculator.HTMLforTypeBadge(
         typeString, display.isLarge() ? 44 : 34));
   },
   isStringRepresentation: function (str) {
      var parts = str.split(" ");
      if (parts.length != 2) return false;
      if (!parts[0].match('[1-8]?[0-9](?:\.[0-9]{1,8})?[N|S]')) return false;
      if (!parts[1].match('(?:1[0-7][0-9]|[1-9]?[0-9])(?:\.[0-9]{1,8})?[E|W]')) return false;
      return true;
   },
   fromString: function (location) {
      var parts = location.split(" ");
      var lat = parseFloat(parts[0].slice(0, -1));
      if (parts[0].indexOf("S") > 0) lat = -lat;
      var lon = parseFloat(parts[1].slice(0, -1));
      if (parts[1].indexOf("W") > 0) lon = -lon;
      return new Point.instance(lat, lon);
   },
   toDisplay: function (obj) {
      display.showImageFromURL(
         "http://maps.google.com/maps/api/staticmap?sensor=false&center=-34.397,150.644&zoom=8&size=420x640");
   },
   eval: function (obj) { this.toDisplay(obj); },
   ConvertDDToDMS: function (val, isLongitude) {
      var dir = val < 0 ? isLongitude ? 'W' : 'S' : isLongitude ? 'E' : 'N';
      var deg = 0 | (val < 0 ? val = -val : val);
      var min = 0 | val % 1 * 60;
      var sec = (0 | val * 60 % 1 * 6000) / 100;
      return deg + "°" + min + "'" + sec + "\"" + dir;
   },
   instance: function (lat, lon) {
      this.lat = lat;
      this.lon = lon;
      this.type = Point.type;
   },

   radians: function (deg) { return deg * (Math.PI / 180); },

   "+": function (a, b) { return new Line.instance(a, b); }

}; calculator.registerType(Point);

/* Line type definition */
var Line = {
   type: "line",
   units: "km",
   isLoaded: true,
   toString: function (obj) {
      return obj.lastdist.toFixed(4)
         + ", " + obj.totaldist.toFixed(4);
   },
   toHTML: function (obj) {
      var typeString = Line.type;
      var stringForType = Line.toString(obj);
      return (stringForType + " (" + this.units + ")" +
         calculator.HTMLforTypeBadge(typeString, display.isLarge() ? 30 : 25));
   },

   distance: function (pnt1, pnt2) {
      var R = 6371;
      var φ1 = Point.radians(pnt1.lat);
      var φ2 = Point.radians(pnt2.lat);
      var Δφ = Point.radians(pnt2.lat - pnt1.lat);
      var Δλ = Point.radians(pnt2.lon - pnt1.lon);

      var a = Math.sin(Δφ / 2) * Math.sin(Δφ / 2) + Math.cos(φ1) * Math.cos(φ2) *
       Math.sin(Δλ / 2) * Math.sin(Δλ / 2);
      var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));

      var d = R * c;
      return d;
   },
   instance: function (a, b) {
      this.points = [a, b];
      this.lastdist = Line.distance(a, b);
      this.totaldist = this.lastdist;
      this.type = Line.type;
   },

   addPoint: function (a, p) {
      a.lastdist = Line.distance(a.points[a.points.length - 1], p);
      a.totaldist += a.lastdist;
      a.points.push(p);
      return a;
   },

   "+": function (a, p) { return Line.addPoint(a, p);   }

}; calculator.registerType(Line);


The data in the screen shots is the same.
One is with Point.format set to Decimal Degrees, the other set to Degrees Minutes Decimal Seconds.
ebkwall
 
Posts: 22
Joined: Tue Apr 05, 2016 2:29 pm

Re: Project: Geography Datatype

Postby oliver » Mon Apr 18, 2016 8:20 pm

Sounds and looks all good. I think the only thing you really need is an onload() function that defines a toGeo (or similar) function that invokes your factory function.

Invoking that function will then create an object of your type. (And it will appear with a badge on the stack.)

The static functions defined on your class are discovered by the calculator and invoked when at at least one of the operands is of the type the class is defining.

See Color, Chem, BigInt for examples of having a onload() function with said creator functions.

Good work; looks like you're basically there!
oliver
Site Admin
 
Posts: 433
Joined: Sat May 01, 2010 2:11 pm


Return to Extensions

Who is online

Users browsing this forum: No registered users and 2 guests

cron