• Hi Folks -

    Today, I encountered a memory leak with a simple bit of code that's interfacing with a GPS module using the GPS library. Specifically, about 2-3 blocks of memory were leaking every 5 seconds (or so) until the GPS got a 'satellite lock'. This may have something to do with how my module transmits data pre-lock, or could be a mistake that I've made (as I'm new to Espruino) . In case it's relevant, the GPS Module that I'm using is the Adafruit 'Ultimate GPS Module' with MTK3339 chipset.

    Initially, I thought the memory leak was something in my code, and I was in the process of tracking that down when the GPS got a 'lock' and the memory leak stopped. I stripped my code down to the minimum, and grabbed three process.memory() and trace() dumps, two before the 'satellite lock', and one after.

    Here's the code that I was testing this with:

    Serial4.setup(9600,{tx:C10,rx:C11});
    var gps = require("GPS").connect(Serial4, function (data) {
    		if ((data.time.substr(-2))=="00") {
    			console.log("time = "+data.time);
    			console.log("lat = "+data.lat);
    			console.log("lon = "+data.lon);
    		}
    });
    

    Immediately after uploading the code, process.memory() returned the info below. The matching trace file is called 'trace-start.txt'

    {"free":1654,"usage":146,"total":1800,"h­istory":70,"stackEndAddress":536909536,"­flash_start":134217728,"flash_binary_end­":134435300,"flash_code_start":134443008­,"flash_length":262144}
    

    A few minutes later process.memory() returned the info below. The matching trace file is called 'trace-422.txt'

    {"free":1378,"usage":422,"total":1800,"h­istory":70,"stackEndAddress":536909536,"­flash_start":134217728,"flash_binary_end­":134435300,"flash_code_start":134443008­,"flash_length":262144}
    

    Finally, the satellite locked when 576 blocks of memory had been used, and the memory leak stopped. process.memory() returned the info below. The matching trace file is called 'trace-satlock.txt'

    {"free":1224,"usage":576,"total":1800,"h­istory":88,"stackEndAddress":536909536,"­flash_start":134217728,"flash_binary_end­":134435300,"flash_code_start":134443008­,"flash_length":262144}
    

    I'd be interested in anyone's thought of ideas on this problem.

    Thanks.


    3 Attachments

  • Hi - thanks for letting me know, and for including the traces... That's an odd one - there doesn't appear to be any obvious cause of the leak. The GPS code is actually pretty simple, see: https://github.com/espruino/EspruinoDocs­/blob/master/devices/GPS.js

    All I can think is that the GPS doesn't send any newline characters while it is searching for a lock, but that doesn't explain the permanent leak.

    Unfortunately as it is, it's hard to see what the problem is, or what the data being read is. Can you try:

    var exports = {};
    
    function handleGPSLine(line, callback) {
      var tag = line.substr(3,3);
      if (tag=="GGA") {
        var d = line.split(",");
        var dlat = d[2].indexOf(".");
        var dlon = d[4].indexOf(".");
        callback({
          time : d[1].substr(0,2)+":"+d[1].substr(2,2)+":­"+d[1].substr(4,2),
          lat : (parseInt(d[2].substr(0,dlat-2),10)+pars­eFloat(d[2].substr(dlat-2))/60)*(d[3]=="­S"?-1:1),
          lon : (parseInt(d[4].substr(0,dlon-2),10)+pars­eFloat(d[4].substr(dlon-2))/60)*(d[5]=="­W"?-1:1),
          fix : parseInt(d[6],10),
          satellites : parseInt(d[7],10),
          altitude : parseFloat(d[9])
        });
      }
    }
    
    
    exports.connect = function(serial, callback) {
      var gps = { line : "" };
      serial.onData(function(d) {
        if (d.data=="\n") {
          var line = gps.line;
          gps.line="";
          handleGPSLine(line, callback);      
        } else gps.line+=d.data;
      });
      return gps;
    }
    
    Serial4.setup(9600,{tx:C10,rx:C11});
    var gps = exports.connect(Serial4, function (data) {
            if ((data.time.substr(-2))=="00") {
                console.log("time = "+data.time);
                console.log("lat = "+data.lat);
                console.log("lon = "+data.lon);
            }
    });
    

    Then you should be able to check on what gps.line is. Perhaps you could also change the beginning of serial.onData(function(d) { to:

    serial.onData(function(d) {
      USB.write(d);
      ...
    

    That'll spam loads of stuff over the console, but it will show you what data Espruino is getting from the GPS while it's waiting for a lock.

  • I recently build a GPS data logger , based entirely on Espruino Javascript, recording data onto a SD card at a 10 Hz rate.
    Please find the relevant code here.
    No problems at all except those coming from missing fixes of GPS in "urban canyons".

  • Please find here the example firmware

  • File not uploaded, so I copied the code here.
    Please find here the example firmware

    //======================================­========================================­====
    // Copyright (c) 2014 Giovanni Varasano.
    // v4 - last update 06/06/2014
    // ========================================­========================================­==
    
    var fs = require("fs");
    
    var  ST = { IDLE: 0, FIX:1, RUN:2 };
    tmrId=0;
    file_ok=0, gps_ok = 0;
    gps_path="";
    gps_date= "", gps_time=""; gps_msec=0;
    gps_lat=0, gps_lon=0, gps_speed =0; gps_course=0;
    ir_red=0.0,ir_nir=0.0,irr_edg=0.0,ir_ndr­e=0.0,ir_ndvi=0.0;
    
    //--------------------------------------­------------------------------------
    //--------------------------------------­------------------------------------
    var WORK    = LED2; 
    var FIX     = LED3;
    var STOP    = LED4; 
    
    // ----------------------------------------­-----------------
    // ----------------------------------------­-----------------
    btn_state = false;
    var btn_id = setWatch(function(e) { btn_state = !btn_state; } ,B6,{ repeat : true, edge : "both", debounce : 350});
    
    //--------------------------------------­------------------------------------
    //--------------------------------------­------------------------------------
    function onInit() {
      setTimeout(function(){
        file_ok=0, gps_ok = 0;
        Serial6.setup(19200, {rx:C7,tx:C6});
        Serial6.print("$JBAUD,115200\r\n");
        
        setTimeout(function(){
            Serial6.setup(115200, {rx:C7,tx:C6});
            Serial4.setup(38400, {rx:C11,tx:C10});
            tracking("RMC");
            tracking("GGA");
            tmrId = go_ahead();
           }, 3000 );
        }, 2000 );
    }
    
    //--------------------------------------­------------------------------------
    //--------------------------------------­------------------------------------
    function go_ahead() {
        state=ST.IDLE
        return  setInterval( function() {
             switch(state) {
    
                case ST.IDLE: file_ok=0;STOP.set();FIX.reset();WORK.re­set();
                             if (btn_state ) { STOP.reset(); state=ST.FIX; }
    	     	         break;
    
                case ST.FIX: if (!btn_state ) {  state=ST.IDLE }
                             FIX.set();
                             if(file_ok === 1) {FIX.reset();state=ST.RUN;work=WORK.star­tFlashing(250);}
    	                 break;
    
                case ST.RUN: if (!btn_state ) {state=ST.IDLE; WORK.stopFlashing(work);}
                             save_data();
    	                 break;
            }
        },50); 
    }
    
    
    //--------------------------------------­------------------------------------
    //--------------------------------------­------------------------------------
    function save_data() {
       if ( gps_ok ) {
         var s_data  = gps_date+","+gps_time+"."+gps_msec+","+g­ps_speed+","+gps_course+",";
         s_data += utm.toString()+","+ir_red+","+ir_nir+","­+irr_edg+","+ir_ndre+","+ir_ndvi+"\r\n";­
         fs.appendFileSync(gps_path,s_data);
         gps_ok=0;
       }
    }
    
    //--------------------------------------­------------------------------------
    //--------------------------------------­------------------------------------
    function LatLng(lat,lng,alt) {
      this.lat = lat;
      this.lng = lng;
      this.alt = alt;
      this.toUTMRef = LatLngToUTMRef;
      this.toString = LatLngToString;
    }
    
    //--------------------------------------­------------------------------------
    //--------------------------------------­------------------------------------
    function LatLngToString() {
      return "(" + this.lat + ", " + this.lng + ")";
    }
    
    
    //--------------------------------------­------------------------------------
    //--------------------------------------­------------------------------------
    function UTMRef(easting, northing, altitude, latZone, lngZone) {
      this.easting  = Number(easting).toFixed(3);   
      this.northing = Number(northing).toFixed(3);  
      this.altitude = Number(altitude).toFixed(3); 
      this.latZone = latZone;
      this.lngZone = lngZone;
     
      this.toString = UTMRefToString;
    }
    
    //--------------------------------------­------------------------------------
    //--------------------------------------­------------------------------------
    function UTMRefToString() {
      return this.easting + "," + this.northing+ ","+this.altitude+","+this.latZone+this.­lngZone+"";
    }
    
    //--------------------------------------­------------------------------------
    //--------------------------------------­------------------------------------
    function RefEll(maj, min) {
      this.maj = maj;
      this.min = min;
      this.ecc = ((maj * maj) - (min * min)) / (maj * maj);
    }
    
    //--------------------------------------­------------------------------------
    //--------------------------------------­------------------------------------
    function LatLngToUTMRef() {
      var wgs84 = new RefEll(6378137, 6356752.314);
      var UTM_F0   = 0.9996;
      var a = wgs84.maj;
      var eSquared  = wgs84.ecc;
      var longitude = this.lng;
      var latitude  = this.lat;
      var altitude  = this.alt;
    
      var latitudeRad   = latitude  * (Math.PI / 180.0);
      var longitudeRad  = longitude * (Math.PI / 180.0);
      var longitudeZone = Math.floor((Number(longitude) + 180.0) / 6.0) + 1;
      
      if (latitude >= 56.0  && latitude < 64.0 && longitude >= 3.0  && longitude < 12.0) {
        longitudeZone = 32;
      }
    
      if (latitude >= 72.0 && latitude < 84.0) {
        if (longitude >= 0.0 && longitude < 9.0) {
          longitudeZone = 31;
        } else if (longitude >= 9.0 && longitude < 21.0) {
          longitudeZone = 33;
        } else if (longitude >= 21.0 && longitude < 33.0) {
          longitudeZone = 35;
        } else if (longitude >= 33.0 && longitude < 42.0) {
          longitudeZone = 37;
        }
      }
      
      var longitudeOrigin = (longitudeZone - 1) * 6 - 180 + 3;
      var longitudeOriginRad = longitudeOrigin * (Math.PI / 180.0);
    
      
      var UTMZone = getUTMLatitudeZoneLetter(latitude);
      
      ePrimeSquared = (eSquared) / (1 - eSquared);
    
      var n = a / Math.sqrt(1 - eSquared * Math.sin(latitudeRad) * Math.sin(latitudeRad));
      var t = Math.tan(latitudeRad) * Math.tan(latitudeRad);
      var c = ePrimeSquared * Math.cos(latitudeRad) * Math.cos(latitudeRad);
      var A = Math.cos(latitudeRad) * (longitudeRad - longitudeOriginRad);
    
     var M = a * (
        (1  - eSquared / 4 - 
         3 * eSquared * eSquared / 64  - 
         5 * eSquared * eSquared * eSquared / 256)  * latitudeRad  - 
       (3 * eSquared / 8  + 3 * eSquared * eSquared / 32 + 45 * eSquared * eSquared * eSquared / 1024)  * 
       Math.sin(2 * latitudeRad) + (15 * eSquared * eSquared / 256   + 45 * eSquared * eSquared * eSquared / 1024)  * 
       Math.sin(4 * latitudeRad)  - (35 * eSquared * eSquared * eSquared / 3072)* Math.sin(6 * latitudeRad));
    
     var UTMEasting =
        (UTM_F0   * n * 
         (A  + (1 - t + c) * Math.pow(A, 3.0) / 6 + 
          (5 - 18 * t + t * t + 72 * c - 58 * ePrimeSquared) * 
          Math.pow(A, 5.0)  / 120)   + 500000.0);
     
      var UTMNorthing = (UTM_F0 * (M   + n * Math.tan(latitudeRad) * 
                  (A * A / 2 + (5 - t + (9 * c) + (4 * c * c)) *
              Math.pow(A, 4.0) / 24 + (61 - (58 * t) + (t * t) + (600 * c) - 
         (330 * ePrimeSquared)) * Math.pow(A, 6.0) / 720))
       );
    
      if (latitude < 0) { UTMNorthing += 10000000.0; }
    
      return new UTMRef(UTMEasting, UTMNorthing, altitude, UTMZone, longitudeZone);
    }
    
    
    //--------------------------------------­------------------------------------
    //--------------------------------------­------------------------------------
    function getUTMLatitudeZoneLetter(latitude) {
      if ((84 >= latitude) && (latitude >= 72)) return "X";
      else if (( 72 > latitude) && (latitude >=  64)) return "W";
      else if (( 64 > latitude) && (latitude >=  56)) return "V";
      else if (( 56 > latitude) && (latitude >=  48)) return "U";
      else if (( 48 > latitude) && (latitude >=  40)) return "T";
      else if (( 40 > latitude) && (latitude >=  32)) return "S";
      else if (( 32 > latitude) && (latitude >=  24)) return "R";
      else if (( 24 > latitude) && (latitude >=  16)) return "Q";
      else if (( 16 > latitude) && (latitude >=   8)) return "P";
      else if ((  8 > latitude) && (latitude >=   0)) return "N";
      else if ((  0 > latitude) && (latitude >=  -8)) return "M";
      else if (( -8 > latitude) && (latitude >= -16)) return "L";
      else if ((-16 > latitude) && (latitude >= -24)) return "K";
      else if ((-24 > latitude) && (latitude >= -32)) return "J";
      else if ((-32 > latitude) && (latitude >= -40)) return "H";
      else if ((-40 > latitude) && (latitude >= -48)) return "G";
      else if ((-48 > latitude) && (latitude >= -56)) return "F";
      else if ((-56 > latitude) && (latitude >= -64)) return "E";
      else if ((-64 > latitude) && (latitude >= -72)) return "D";
      else if ((-72 > latitude) && (latitude >= -80)) return "C";
      else return 'Z';
    }
    
    // ----------------------------------------­-------------------
    // ----------------------------------------­----------
    var Checksum = function(sentence, checksum) {
          var q, c1, c2, i;
          i = 1; 
          c1 = sentence.charCodeAt(i); 
          for( i = 2; i < sentence.length; ++i) {
            c1 = c1 ^ sentence.charCodeAt(i);
          }
          c2 = parseInt(checksum, 16);
          return (c1  === c2); 
    };
    
    // ----------------------------------------­-------------------
    // ----------------------------------------­----------
    var m_hex = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'];
    
    // ----------------------------------------­-------------------
    // ----------------------------------------­----------
    var toHexString = function(v) {
          var lsn, msn;
          msn = (v >> 4) & 0x0f;
          lsn = (v >> 0) & 0x0f;
          return m_hex[msn] + m_hex[lsn];
    };
    
    // ----------------------------------------­-------------------
    // ----------------------------------------­----------
    var computeChecksum = function(sentence) {
          var c1;
          var i;
          i = 1; 
          c1 = sentence.charCodeAt(i);
          for( i = 2; i < sentence.length; ++i) {
            c1 = c1 ^ sentence.charCodeAt(i);
          }
          return '*' + toHexString(c1);
    };
    
    
    // ----------------------------------------­-------------------
    // ----------------------------------------­-------------------
    function handleGPSLine(line ,callback) {
       var c = line.split("*");
      if ( Checksum(c[0],c[1]) ) {
          var tag = line.substr(3,3);
          var d = line.split(",");
          if (tag=="GGA") {  
             var dlat = d[2].indexOf(".");
             var dlon = d[4].indexOf(".");
             callback({
                tag    : tag,
                time : d[1].substr(0,2)+":"+d[1].substr(2,2)+":­"+d[1].substr(4,2),
                msec : (parseInt(d[1].substr(7,2),10)*10),
                lat : ((parseInt(d[2].substr(0,dlat-2),10)+par­seFloat(d[2].substr(dlat-2))/60)*(d[3]==­"S"?-1:1)).toFixed(9),
                lon : ((parseInt(d[4].substr(0,dlon-2),10)+par­seFloat(d[4].substr(dlon-2))/60)*(d[5]==­"W"?-1:1)).toFixed(9),
                fix : parseInt(d[6],10),
                alt : parseFloat(d[9])
             });
          }
          if (tag=="RMC") { 
             callback({
              tag    : tag,
              time   : d[1].substr(0,2)+":"+d[1].substr(2,2)+":­"+d[1].substr(4,2),
              valid  : d[2],
              speed  : parseFloat(d[7])*1.852,
              course : parseFloat(d[8]),
              date   : d[9].substr(0,2)+"/"+d[9].substr(2,2)+"/­"+d[9].substr(4,2),
              path   : d[9].substr(4,2)+d[9].substr(2,2)+d[9].s­ubstr(0,2)+"_"+d[1].substr(0,2)+d[1].sub­str(2,2)+".txt"
          		});
         }
      }
    }
    
    // ----------------------------------------­-------------------
    // ----------------------------------------­----------
    function fix_gps_data(data) {
       if ( data.fix > 0 ) {
            gps_ok = 1;
            gps_time=data.time;
            gps_msec=data.msec;
            gps_lat=data.lat;
            gps_lon=data.lon;
            var ll = new LatLng(data.lat,data.lon,data.alt);
            utm = ll.toUTMRef();
       } else {
            gps_ok = 0;
       } 
    }
    
    // ----------------------------------------­-------------------
    // ----------------------------------------­----------
    function get_gps_data(data) {
        if(data.tag == "GGA" ) {  fix_gps_data(data); }
        if(data.tag == "RMC" ) {  fix_data(data); }
    }
    
    // ----------------------------------------­-------------------
    // ----------------------------------------­-------------------
    function fix_data(data) {
        if ( data.valid=="A" ) {
          if ( file_ok === 0) {
    	    file_ok=1;
        	    gps_date = data.date;
        	    gps_path = data.path;
                var head = "# File: "+gps_path+" created on "+data.date+" "+data.time+"\r\n";
        	    fs.appendFileSync(gps_path,head);
          }
          gps_speed  = data.speed;
          gps_course = data.course;
        }
    }
    // ----------------------------------------­-------------------
    // ----------------------------------------­----------
    function fix_irs_data(data) {
        ir_red   = data.red;
        ir_nir   = data.nir;
        irr_edg  = data.red_edg;
        ir_ndre  = data.ndre;
        ir_ndvi  = data.ndvi; 
    }
      
    
    // ----------------------------------------­-----------
    //
    
    // ----------------------------------------­----------
    function handleIRSLine(line ,callback) {
         var d = line.split(",");
         callback({ red     : parseFloat(d[0]),
    	        nir     : parseFloat(d[1]),
                    red_edg : parseFloat(d[2]),
                    ndre    : parseFloat(d[3]),
                    ndvi    : parseFloat(d[4]) });
    }
    
    // ----------------------------------------­-------------------
    // ----------------------------------------­----------
    Serial6.onData(function (e) {  
        if (e.data=="\n") { 
          var line = gpsLine;
          gpsLine="";
          handleGPSLine(line ,function(data) { get_gps_data(data); } );
        } else gpsLine+=e.data;
      } 
    );
    
    // ----------------------------------------­-------------------
    // ----------------------------------------­----------
    Serial4.onData(function (e) {  
        if (e.data=="\n") { 
          var line = irsLine;
          irsLine="";
          handleIRSLine(line ,function(data) {  fix_irs_data(data); } );
        } else irsLine+=e.data;
      } 
    );
    
    // --------------------------------------
    // --------------------------------------
    var tracking = function(cmd ) {
      var str = "$J";
      if (cmd =="GGA" ) { 
          str += "ASC,GP"+cmd+",10";
      } else if ( cmd == "RMC" ) {
           str += "ASC,GP"+cmd+",1";
      } else { }
      if (cmd == "OFF") {  str += cmd; }
      str += "\r\n";
      Serial6.print(str);
    };
    
    // ----------------------------------------­------
    // ----------------------------------------­------
    Pin.prototype.startFlashing = function(period) { 
      var on = false;
      var pin = this;
      return setInterval(function() { on = !on; digitalWrite(pin, on); }, period);
    };
    
    // ----------------------------------------­------
    // ----------------------------------------­------
    Pin.prototype.stopFlashing = function(stop ) { 
        var pin = this;
        clearInterval(stop);digitalWrite(pin,0);­
    };
    
    onInit();
    
  • /Users/gv/Desktop/screenshot_667.png

  • Please find here a log view corresponding to 4 different acquisitions or travels.


    1 Attachment

    • screenshot_667.png
  • Thanks folks for your feedback and info.

    @user6350 - Thank you so much for providing this awesome code-base! I'll definitely leverage what I can from that once I start working on the actual GPS project again. (Right now I'm kind of obsessed with understanding what is causing this leak.)

    @Gordon - I'll help out however I can to track down this memory leak, as it might effect other modules and users as well.

    I have limited time today, but I was able to log the direct serial output of the GPS before and after 'lock', so we can examine the data-stream.

    Here's a sample from the data-stream, before GPS-lock

    $GPRMC,235948.318,V,,,,,0.00,0.00,050180­,,,N*4A
    $GPVTG,0.00,T,,M,0.00,N,0.00,K,N*32
    $GPGGA,235949.318,,,,,0,0,,,M,,M,,*42
    $GPGSA,A,1,,,,,,,,,,,,,,,*1E
    

    Here's a sample from the data-stream, after GPS-lock (Note: the checksum bytes won't be accurate, as I've anonymized the location data.)

    $GPGGA,044236.000,4807.0380,N,01131.000,­W,1,8,0.97,545.4,M,-46.9,M,,*65
    $GPGSA,A,3,21,29,25,05,26,15,18,12,,,,,1­.35,0.97,0.94*06
    $GPRMC,044237.000,A,4807.0380,N,01131.00­0,W,0.06,545.4,160614,,,A*76
    $GPVTG,327.20,T,,M,0.06,N,0.11,K,A*3F
    

    I manually executed some of the library definitions with pre-lock data. Note: I have a similar output generated with post-lock data, but since it executes as expected, i'm not sure it adds any value. I can post it if anyone thinks it will help.

    var line = "$GPGGA,235952.319,,,,,0,0,,,M,,M,,*49"
    ="$GPGGA,235952.319,,,,,0,0,,,M,,M,,*49"­
    var d = line.split(",")
    =[
    	  "$GPGGA",
    	  "235952.319",
    	  "",
    	  "",
    	  "",
    	  "",
    	  "0",
    	  "0",
    	  "",
    	  "",
    	  "M",
    	  "",
    	  "M",
    	  "",
    	  "*49"
    	]
    var dlat = d[2].indexOf(".");
    =-1
    var lat = (parseInt(d[2].substr(0,dlat-2),10)+pars­eFloat(d[2].substr(dlat-2))/60)*(d[3]=="­S"?-1:1);
    =NaN
    

    I'm wondering if there could be a leak caused by one of the string parsing functions used to calculate the latitude, longitude, fix, satellite, or altitude vars, when using a '-1' parameter and/or when the result is "NaN". Since these functions execute several times a second, even a small leak would become significant very quickly.

    Alternatively, could this be some sort of buffer overflow with the Serial Port? I've verified that my module is running at 9600 baud, so I don'd see how this is possible.

    I have the following tests planned for tomorrow:

    1. I'll switch the code to using your library as a local function as you suggested.
    2. I'll comment out sections of the code and try to create the smallest piece of code that still exhibits this problem.
    3. I'll remove the GPS & Serial functions entirely, and make a function that uses the parsing functions (as defined in the library) to parse our pre-defined sample data taken from the pre-lock GPS capture. Then set this function to execute once a second, and monitor the memory usage.

    I believe these steps should help us determine the root cause of the leak. Are there other test that I should try?

    Are there other additional environment settings that I should capture in addition to process.memory() and trace() ?

  • @azrobbo thanks for your help! you were spot on about it being nothing to do with the GPS or Serial. It's String.indexOf.

    I took your code, ran it 10 times, and measured process.memory before and after, then I just started removing stuff. I finally got down to "".indexOf("."). I'll put a fix in for it now...

    The new version of Espruino's got some pretty big changes, so it might be a while before I release it, just to make sure it's ok.

  • @Gordon thank you so much for tracking this down! I can easily code around this until the next release comes out.

    Cheers!

  • Edit: My apologies. I found it :)

    =========================
    @user6350 thanks so much for this wonderful code base to learn from!

    I have one question concerning your code. On lines 38 and 39 you have the following function calls:

    tracking("RMC");
    tracking("GGA");
    

    However I don't see where this tracking function is defined. Could you please clarify?

    Many thanks.
    Roy

  • @user6350, working on getting Espruino to understand my 'old' marine #GPS, I read through your code in post http://forum.espruino.com/conversations/­1578/#comment26928. I tried to understand getUTMLatitudeZoneLetter(latitude) {... #latitudezone function. Analyzing the implied algorithm and underlaying data values and structure, a How about:... popped up in my head:

    How about arithmetically coming up with an index pointing at a character in a string of the UTM latitude zone letters:

    And this would be the code:

    function getUTMLatitudeZoneLetter4(latitude) { 
     return ( 
      ((latitude >= -80) && (latitude <= 84)) // defined, valid range
       ? "CDEFGHJKLMNPQRSTUVWX".charAt(Math.floor­((latitude * 1 + 80) / 8)) // valid
       : "Z" // undefined and invalid
      );
    }
    

    The two-termed logical AND expressions, for example, ((84 >= latitude) && (latitude >= 72))
    threw me off. Two-termed logical expressions are usually only used for dealing with non-contiguous ranges.

    To verify the How about, I first morphed the given, quite redundant code into a more concise form - of which one you find below.

    function getUTMLatitudeZoneLetter3(latitude) {
      return (                     // interval:
          (latitude >   84) ? "Z" // invalid
        : (latitude >=  72) ? "X" // [72..84] - incl 72N .. incl 84N
        : (latitude >=  64) ? "W" // [64..72) - incl 64N .. excl 72N
        : (latitude >=  56) ? "V"
        : (latitude >=  48) ? "U"
        : (latitude >=  40) ? "T"
        : (latitude >=  32) ? "S"
        : (latitude >=  24) ? "R"
        : (latitude >=  16) ? "Q"
        : (latitude >=   8) ? "P"
        : (latitude >=   0) ? "N"
        : (latitude >=  -8) ? "M"
        : (latitude >= -16) ? "L"
        : (latitude >= -24) ? "K"
        : (latitude >= -32) ? "J"
        : (latitude >= -40) ? "H"
        : (latitude >= -48) ? "G"
        : (latitude >= -56) ? "F"
        : (latitude >= -64) ? "E"
        : (latitude >= -72) ? "D"
        : (latitude >= -80) ? "C" // [-80..-72) - iexcl 72S .. incl 80S
        :                     "Z" // invalid
        );
    }
    

    After that it became clear how the arithmetic version would look like for the computational approach. Separating the defined range from the undefined ones is though still best done with the comparison approach. The values in the defined range are converted by a formula into range of 0..18, just parallel matching the "CDE...X" string. Note that letters I and O are 'skipped'.

    Luckily, the irregular interval [72..84] N still works for the divide by 8, because the top value is less than 2 * 8 and collapse with Math.floor() to the the range of [72..80).

    I'd like to point out a nice, genuine JavaScript language behaviors: #operatoroverloading, and #typeconversion. You may wonder about the multiplication by 1 in the correction term (latitude * 1 + 80) to base the defined range to begin with 0. JavaScript has the nice feature - Java-ists call it flaw - of going further with auto type conversion than many other languages - especially (strongly, static) typed ones. When the function argument latitude is as a string - which it most likely is when reading plainly from the wire) - then types matter expressions operator overloading.

    Therefore, the value of (latitude + 80) with latitude having the string value "-80", the expression ("-80" + 80) evaluates to "-8080" string, because the "-80" is a string type and defines the type precedence of the operator overloading: + operator becomes string concatenation and hence the result is a string too. JavaScript will convert the number 80 into a string to accommodate the concatenation. Reordering and taking the number 80 first - (80 + latitude) - does not help either. The number 80 is operator overloading happens again to accommodate the "-80" string. Multiplying the latitude by 1 - (latitude * 1 + 80) - solves the issue with no (real) harm or value effect, even when latitude is (already) a number. Multiplication has no operator overloading and numeric is the only option. The "-80" string is converted to the number -80 to accommodate it and the expression evaluates to 0 - just as expected ;-).

    Furthermore, notice the opening parenthesis on the same line of the return statement. This is because of another JavaScript genuine 'feature', or #returnpitfall, #jspitfall: If a line feed follows a return, the return is considered as complete and the function returns #undefined, no matter what keeps going on in the next line(s). I could have of course put something relevant after the return keyword to avoid it, for example, the logical expression for separating the valid from the invalid ranges:

    function getUTMLatitudeZoneLetter4(latitude) { 
     return ((latitude >= -80) && (latitude <= 84)) // defined, valid range
      ? "CDEFGHJKLMNPQRSTUVWX".charAt(Math.floor­((latitude * 1 + 80) / 8)) // valid
      : "Z"; // undefined and invalid(?)
    }
    

    Try it yourself with the uploaded .html file... ;-).

    For some (random and visualizing) unit testing I like to have a visual UI environment that I can drive with HTML5/JavaScript. Therefore I created a simple file-protocol served .html file. See uploaded screenshot and .html file. You can download the .html file, look at the various implementations, open it in a browser (with no harm) and play with it.


    2 Attachments

  • Post a reply
    • Bold
    • Italics
    • Link
    • Image
    • List
    • Quote
    • code
    • Preview
About

GPS code/module/library leaking memory before 'satellite lock' occurs

Posted by Avatar for azrobbo @azrobbo

Actions