Storing GPS Location in Photo Pixels

A few years ago I was working on a project using a multi-rotor UAS to monitor weather.  While out in the field, the user needed to be able to see the location of the UAS on a satellite map.  The UAS had a GPS and was capable of transmitting that back to the laptop running custom ground station software; however, a constant connection to the internet would be necessary to get satellite maps from Google and many places this would be used won’t have a reliable signal.  So I rolled my own solution to fetch the map from Google ahead of time and save it as an image file, which works fine since the location would be known in advance.  The challenge was that I needed to have GPS coordinates associated with the image.  Instead of having the GPS coordinates in a separate file, I decided to alter the pixels in the image to store the GPS coordinates so that only one file was necessary.

Now before going into the how-to (which is mostly so that I will remember it in the future) let me state that this isn’t really a good option moving forward.  The Google APIs have gotten so much better that working with a single image seems slow and antiquated.  Plus, cell phones have improved enough that you can generate a wifi signal in many more places than you could five years ago.  But this may be useful for other reasons so I’ll just document it here.

Here is a screenshot of the map creator program written in Processing.  It uses the GoogleMapper library to get an image from Google Maps and to get the GPS coordinates of a particular pixel.  The image is moved using the arrow buttons in the top left corner and the zoom buttons below that.  A filepath for saving the image can be typed below the image and saved when clicking on the save button.  The Processing code as well as the code for the button and text box classes I made can be found at the bottom of this post.

SAMRAMP_Map_Creator.png

This method of getting the map image from Google essentially works by trying to load an image from a url where the components of the URL define the map that you want.  The image below shows one such URL.  The black letters are constants that are in every such URL, while the red characters can be changed to alter the map that is received.

map_api.png

After clicking save in the Map Creator, the program converts the map image into a series of pixels that can be individually edited.  It then uses the GoogleMapper library to get the GPS coordinates of the upper left pixel, the bottom right pixel, and the center pixel.  These coordinates were then converted into ascii text then into numbers and saved by altering the colors in the top left corner of the map.  Red values are decided by the latitude; green values are decided by longitude. The first row is the coordinate of the upper left corner of the map. The second row is the coordinate of the lower right corner. The third row is the center of the map.  Blue values are not used in the first two, but the zoom level is stored in the blue color values of the center location.

This image shows a zoomed in view of the top left corner where you can clearly see that the pixels have been altered.

Untitled-2.png

 

And this image shows a very detailed example of how these color values are created.  The latitude and longitude are converted to ascii codes and the R value is the latitude and the G value is the longitude.

embedding_example.png

And that’s how I stored the GPS coordinate values as color values in a PNG file.  It meant that I still had GPS information stored with the image rather than a separate file.  But keep in mind that I’m only writing this so that I’ll remember what happened – this is not the route I would go down now.

import googlemapper.*;

PImage map;
PImage destination;
PFont myFont;
GoogleMapper gMapper;

Button roadmapButton = new Button(10, 290, 80, 20, "Roadmap");
Button satelliteButton = new Button(10, 320, 80, 20, "Satellite");
Button terrainButton = new Button(10, 350, 80, 20, "Terrain");
Button hybridButton = new Button(10, 380, 80, 20, "Hybrid");
Button saveButton = new Button(590, 410, 100, 20, "Save Image");

TextBox fileNameBox = new TextBox(100, 410, 480, 20, "/Users/mwwalk/Desktop/map.png");

int zoomLevel = 17;

float mapCenterLat = 35.18771119;
float mapCenterLon = -97.44038227;
String mapType = GoogleMapper.MAPTYPE_HYBRID;
int mapWidth=600;
int mapHeight=400;
String mapLink="";
String basic = "http://maps.googleapis.com/maps/api/staticmap?";
String location="center=" + mapCenterLat +"," + mapCenterLon;
String type="hybrid";

int zoomInLocationX = 50;
int zoomInLocationY = 160;
int zoomOutLocationX = 50;
int zoomOutLocationY = 210;
int movementLocationX = 50;
int movementLocationY = 45;

void getNewMap()
{
    mapLink = basic + location + "&zoom=" + zoomLevel + "&size=" + mapWidth + "x" + mapHeight + "&maptype=" + type;
  //mapLink = basic + location + "&zoom=" + zoomLevel + "&size=" + mapWidth + "x" + mapHeight + "&sensor=false&visual_refresh=true&maptype=" + type;
  map = loadImage(mapLink, "png");
  gMapper = new GoogleMapper(mapCenterLat, mapCenterLon, zoomLevel, mapType, mapWidth, mapHeight);
}

public void setup()
{
  size(700, 440);

  myFont = createFont("Helvetica", 12);
  textFont(myFont, 12);

  getNewMap();
  println(mapLink);
  println("width = " + map.width);
  println("height = " + map.height);
}

public void draw()
{
  background(180);

  //draw map
  image(map, 100, 0);
  fill(0);
  strokeWeight(1);
  stroke(0);
  line(400, 0, 400, 400);
  line(100, 200, 700, 200);
  noStroke();

  // Draw Zoom Buttons
  fill(255);
  ellipse(zoomInLocationX, zoomInLocationY, 40, 40);
  ellipse(zoomOutLocationX, zoomOutLocationY, 40, 40);
  fill(0);
  stroke(0);
  strokeWeight(2);
  line(zoomInLocationX-7, zoomInLocationY, zoomInLocationX+7, zoomInLocationY);
  line(zoomInLocationX, zoomInLocationY+7, zoomInLocationX, zoomInLocationY-7);
  line(zoomOutLocationX-7, zoomOutLocationY, zoomOutLocationX+7, zoomOutLocationY);
  noStroke();

  // Draw arrows
  fill(255);
  triangle(movementLocationX, movementLocationY-30, movementLocationX+10, movementLocationY-10, movementLocationX-10, movementLocationY-10);
  triangle(movementLocationX, movementLocationY+30, movementLocationX+10, movementLocationY+10, movementLocationX-10, movementLocationY+10);
  triangle(movementLocationX-30, movementLocationY, movementLocationX-10, movementLocationY-10, movementLocationX-10, movementLocationY+10);
  triangle(movementLocationX+30, movementLocationY, movementLocationX+10, movementLocationY-10, movementLocationX+10, movementLocationY+10);
  fill(180);
  ellipse(movementLocationX, movementLocationY, 29, 29);

  roadmapButton.draw();
  satelliteButton.draw();
  terrainButton.draw();
  hybridButton.draw();
  fileNameBox.draw();
  saveButton.draw();
}

void mousePressed()
{
  if (mouseEvent.getClickCount() == 2)
  {
    if (mouseX > 100 && mouseX < 700 && mouseY > 0 && mouseY < 400)     {       zoomLevel++;       mapCenterLat = (float)gMapper.y2lat(mouseY);       mapCenterLon = (float)gMapper.x2lon(mouseX-100);       location = "center=" + mapCenterLat+ "," + mapCenterLon;       getNewMap();     }   }   if (fileNameBox.isClicked())     fileNameBox.hasFocus = 1;   else     fileNameBox.hasFocus = 0;   if (mouseX > movementLocationX-10 && mouseX < movementLocationX+10 && mouseY > movementLocationY-30 && mouseY < movementLocationY-10)   {     mapCenterLat = mapCenterLat + 10/zoomLevel;     getNewMap();     println("UP");   }   if (mouseX > movementLocationX-30 && mouseX < movementLocationX-10 && mouseY > movementLocationY-10 && mouseY < movementLocationY+10)   {     mapCenterLon = mapCenterLon + 10/zoomLevel;     getNewMap();     println("LEFT");   }   if (mouseX > movementLocationX-10 && mouseX < movementLocationX+10 && mouseY > movementLocationY+10 && mouseY < movementLocationY+30)   {     mapCenterLat = mapCenterLat - 10/zoomLevel;     getNewMap();     println("DOWN");   }   if (mouseX > movementLocationX+10 && mouseX < movementLocationX+30 && mouseY > movementLocationY-10 && mouseY < movementLocationY+10)   {     mapCenterLon = mapCenterLon - 10/zoomLevel;     getNewMap();     println("RIGHT");   }   if (mouseX > zoomInLocationX - 20 && mouseX < zoomInLocationX + 20)   {     if (mouseY > zoomInLocationY-20 && mouseY < zoomInLocationY+20)     {       zoomLevel++;       if (zoomLevel > 20)
        zoomLevel = 20;
      getNewMap();
    }
    if (mouseY > zoomOutLocationY-20 && mouseY < zoomOutLocationY+20)
    {
      zoomLevel--;
      if (zoomLevel < 1)
        zoomLevel = 1;
      getNewMap();
    }
  }

  if (saveButton.isClicked())
    saveImage();
  else if (roadmapButton.isClicked())
  {
    type = "roadmap";
    getNewMap();
  }
  else if (satelliteButton.isClicked())
  {
    type = "satellite";
    getNewMap();
  }
  else if (terrainButton.isClicked())
  {
    type = "terrain";
    getNewMap();
  }
  else if (hybridButton.isClicked())
  {
    type = "hybrid";
    getNewMap();
  }
}

void keyPressed()
{
  if (fileNameBox.hasFocus())
    fileNameBox.typed();
}

void saveImage()
{
  int i;
  //mapCenterLat = (float)gMapper.y2lat(mouseY);
  //mapCenterLon = (float)gMapper.x2lon(mouseX-100);

  mapCenterLat = (float)gMapper.y2lat(150);
  mapCenterLon = (float)gMapper.x2lon(300);
  float upperLeftLat = (float)gMapper.y2lat(0);
  float upperLeftLong = (float)gMapper.x2lon(0);
  float lowerRightLat = (float)gMapper.y2lat(300);
  float lowerRightLong = (float)gMapper.x2lon(600);

  String upperLeftLatString = nf(upperLeftLat, 3, 10);
  String upperLeftLongString = nf(upperLeftLong, 3, 10);
  String lowerRightLatString = nf(lowerRightLat, 3, 10);
  String lowerRightLongString = nf(lowerRightLong, 3, 10);
  String middleLatString = nf(mapCenterLat, 3, 10);
  String middleLongString = nf(mapCenterLon, 3, 10);
  String zoomString = nf(zoomLevel, 4, 10);

  if (upperLeftLatString.length() < 15)
    upperLeftLatString = "0" + upperLeftLatString;
  if (upperLeftLongString.length() < 15)
    upperLeftLongString = "0" + upperLeftLongString;
  if (lowerRightLatString.length() < 15)
    lowerRightLatString = "0" + lowerRightLatString;
  if (lowerRightLongString.length() < 15)
    lowerRightLongString = "0" + lowerRightLongString;
  if (middleLatString.length() < 15)
    middleLatString = "0" + middleLatString;
  if (middleLongString.length() < 15)
    middleLongString = "0" + middleLongString;

  println(upperLeftLat);
  println(upperLeftLong);
  println(lowerRightLat);
  println(lowerRightLong);
  println(mapCenterLat);
  println(mapCenterLon);
  println();
  println(upperLeftLatString);
  println(upperLeftLongString);
  println(lowerRightLatString);
  println(lowerRightLongString);
  println(middleLatString);
  println(middleLongString);
  println();
  float a = (upperLeftLat+lowerRightLat)/2;
  float b = (upperLeftLong+lowerRightLong)/2;
  println(a);
  println(b);

  map.loadPixels();
  destination = createImage(map.width, map.height, RGB);
  destination.loadPixels();

  for (int x = 0; x < map.width; x++)
  {
    for (int y = 0; y < map.height; y++ )
    {
      int loc = x + y*map.width;
      destination.pixels[loc] = map.pixels[loc];
    }
  }

  for (i=0; i<15; i++)   {     destination.pixels[i] = color(upperLeftLatString.charAt(i), upperLeftLongString.charAt(i), 0);     destination.pixels[i+600] = color(lowerRightLatString.charAt(i), lowerRightLongString.charAt(i), 0);     destination.pixels[i+1200] = color(middleLatString.charAt(i), middleLongString.charAt(i), zoomString.charAt(i));   }   // We changed the pixels in destination   destination.updatePixels();   // Display the destination   destination.save(fileNameBox.label); } 
  class TextBox {     public int x;     public int y;     public int wide;     public int high;     public int hasFocus;     public int visible;     public int isClickable;     public int fontSize;     public int type;        //1 is everything, 2 is only num, 3 is only letters, 4 is 2 and 3     public color myColor;     public color textColor;     public color strokecolor;     public String label;     public String shortLabel;     public TextBox(int x, int y, int w, int h)     {         this(x, y, w, h, 255, 255, 255, "");     }     public TextBox(int x, int y, int w, int h, String s)     {         this(x, y, w, h, 255, 255, 255, s);     }     public TextBox(int x, int y, int w, int h, int c)     {         this(x, y, w, h, c, c, c, "");     }     public TextBox(int x, int y, int w, int h, int r, int g, int b, String s)     {         this.x = x;         this.y = y;         this.wide = w;         this.high = h;         this.hasFocus = 0;         this.myColor = color(r, g, b);         this.textColor = 0;         this.label = s;         this.visible = 1;         this.isClickable = 1;         this.fontSize = 12;         this.type = 1;     }     public void draw()     {         if (visible==1)         {             strokeWeight(0);             // Make sure text fits in given space.             shortLabel = label;             while (textWidth (shortLabel) > (wide - 10))
                shortLabel = shortLabel.substring(1);

            if (hasFocus == 1)
            {
                noStroke();
                fill(42, 211, 238);
                rect(x-1, y-1, wide+2, high+2);
            }

            fill(myColor);
            noStroke();
            rect(x, y, wide, high);
            fill(textColor);
            textAlign(LEFT, CENTER);
            textFont(myFont, fontSize);
            text(shortLabel, x+2, y+high/2);

            if (hasFocus == 1)
            {
                stroke(0);
                fill(0);
                line(this.x+textWidth(this.shortLabel)+2, this.y+2, this.x+textWidth(this.shortLabel)+2, this.y+20);
            }
        }
    }

    public boolean isClicked()
    {
        if (this.isClickable == 1)
        {
            if (mouseX > this.x && mouseX < this.x + this.wide && mouseY > this.y && mouseY < this.y + this.high)                 return true;             else                 return false;         }         else             return false;     }     public boolean hasFocus()     {         if (this.hasFocus == 1)             return true;         else             return false;     }     public void typed()     {                  if (key == 0x08) // Backspace         {             if (this.label.length() > 0)
            {
                this.label = this.label.substring(0, this.label.length()-1);
            }
        }
        else if (key == 45 || key == 46 || (key >= 48 && key <=57))         {             if (this.type == 1 || this.type == 2 || this.type == 4)                 this.label += key;         }         else if (key >= 65 && key <= 90)         {             if (this.type == 1 || this.type == 3 || this.type == 4)                 this.label += key;         }         else if (key >= 97 && key <=122)         {             if (this.type == 1 || this.type == 3 || this.type == 4)                 this.label += key;         }         else if (key >= 32 && key <=126)         {             if (this.type == 1)                 this.label += key;         }         else if (key == 9) // Tab         {             this.hasFocus = 0;             //longitude.hasFocus = 1;         }     } } 
  class Button {          public int x;          public int y;          public int wide;          public int high;          public int visible;          public int radius;          public int fontSize;          public color myColor;          public color textColor;          public String label;               public Button(int x, int y, int w, int h)          {                  this(x, y, w, h, "");          }          public Button(int x, int y, int w, int h, String s)          {                  this.x = x;                  this.y = y;                  this.wide = w;                  this.high = h;              this.myColor = 255;         this.textColor = 0;          this.label = s;          this.visible = 1;          this.fontSize = 12;         if (wide >= high)
            this.radius = high/4;
        else
            this.radius = wide/4;
    }

    public void draw()
    {
        fill(myColor);
        noStroke();
        rect(x, y+radius, wide, high-2*radius);
        rect(x+radius, y, wide-2*radius, high);
        ellipse(x+radius, y+radius, 2*radius, 2*radius);
        ellipse(x+radius, y+high-radius, 2*radius, 2*radius);
        ellipse(x+wide-radius, y+radius, 2*radius, 2*radius);
        ellipse(x+wide-radius, y+high-radius, 2*radius, 2*radius);
        fill(textColor);
        textAlign(CENTER, CENTER);
        textSize(fontSize);
        text(this.label, x+wide/2, y+high/2);
    }

    public boolean isClicked()
    {
        if (mouseX > x && mouseX < x+wide && mouseY > y && mouseY < y+high)
            return true;
        else
            return false;
    }
}

 

 

 

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s