Playing with the N95 - Use LBS API and so more
In this second tutorial, now we know how to get our Location, we will use it to draw some maps, centered on our location.
This tutorial will just describe a simple Application which draw maps we had generated before, no download in the App here, so the bottleneck will be the data, here I'll just use a few maps around my house I've generated but if you to make a application which cover a big landscape you'll have to find another solution, or pass long time with making maps..
So, First, what are this maps :
The important thing with the maps is that they need to be geolocated, we must know the location of 2 point in the map. So with every image you need to have 2 Location, not very convenient, a simple solution is to use images which are contiguous are have all the same size, so we just need to have one image with 2 Geo located point, and we can guess the Location on all other images, with numerated them with an X and an Y index :

I've made the choice to use OpenGLES API (3D API) for drawing maps, so these ones need to have width and height power of two : 16,32,64,128,256,512.... and use square maps are useful for computing coordinate so I will use 256x256 image.
To identify maps we will have to named the according their X and Y offset, so here is the naming rules I choose for this App :
maps_%d_%d.png :
- X:0 and Y:0 will be saved under maps_0_0.png
- X:1 and Y:0 will be saved under maps_1_0.png
- X:-1 and Y:-1 will be saved under maps_-1_-1.png
- etc...
So Basically, how maps will be drawn by the Application :
When we have our Location, thanks to the part 1 of this tutorial , first thing is to know on wich map we are. To do this we calculate the distance between our Location and the Origin Point of (0,0) maps, the red point, and we divise these distances, along X and Y axes, by the size of one maps. This will give us the indexes, X and Y, of the maps on which we are located.
After we draw this maps, with calculate the good pos on the screen to have our location centerd on the screen.
Finally we draw the maps adjacent to this one :

Perhaps it's not very clear but you'll see calculation will be quite simple.
The next step I'd Like to explain is why I'll use 3D to draw the maps. The problem of using fixed size image is that it's not very convenient to do zoom in or out on the maps, we need, once to resize the maps and, twice, to make new calculation to know were drawing the maps on the screen. With using 3D drawing, the Open GL ES API will do that for us. We just have to draw the maps on a plane, which is for example along an XY plane, and when we move this plane along Z axe we do zoom in or out, the closer we are from the origin, the more we zoom :

So with this solution we don't have to manage zoom, cool isn't it?
And drawing maps on a plane in 3D is nearly the same than drawing on the screen.
Ok last thing we need to know before start the code is how managing texture. Texture in 3D are image which, in our case, will be drawn on a squared plane, there'll be a plane for each texture/maps we will draw.
A CTextureManager class will be create which will load the maps file and transform them to OpenGLES textures.
Ok so let's go with code.
First we will reuse the CActivePositionner we use in the First Part, http://www.newlc.com/playing-n95, but with modifying it a little : We had it a Real Observer to pass the Position to our App :
class MGPsObserver
{
public:
virtual void SetPos(TPositionInfo aInfo) =0;
virtual void SetErrorCode(TInt aCode) =0;
};
class CCActivePositioner : public CActive
{
// C++ constructor
CCActivePositioner(MGPsObserver& aObserver);
(..)
MGPsObserver& iObserver;
};For this App we will create a standard App/View/UI/Document App And most of the code will be in the View Class.
This class will henerits MGPsObserver to gat GPS Location, via SetPos and SetError method.
The view class will use some variable to be able to draw the maps according to the position which are :
TInt iXIdx;
TInt iYIdx;
TReal32 iDeltaX;
TReal32 iDeltaY;iXIdx and iYIdx are x and y indexes of the maps and iDeltaX and iDeltaY are the position of the phone on this map, relative position with a value between 0 and 1.
These variables are updated in the SetPos method, when we have our Location, and use in the Draw method.
we will use also two TReal32 iWidth and iHeight with represent the size, in meters, of a maps, we need them to calculate the precedents variables.
So Now let's see the principals methods of the CMapGpsAppView class :
First we use constants which will represent the Top Left and Bottom Right coordinates of the (0,0) maps, red and blue dot above.
And also a constant defining the path which are located the maps:
//Constant
const TCoordinate KPosTL(61.4482,23.8547);
const TCoordinate KPosBR(61.4497,23.8552);
_LIT(KText,"c:\\Data\\Images\\maps_%d_%d.png");The ConstructL methods
void CMapGpsAppView::ConstructL( const TRect& aRect )
{
// Create a window for this application view
CreateWindowL();
// Set the windows size
SetRect( aRect );
// Activate the window, which makes it ready to be drawn
ActivateL(); in which we
- Initialise OpenGLES :
// EGL variables
eglDisplay = 0;
eglConfig = 0;
eglSurface = 0;
eglContext = 0;
eglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
EGLint iMajorVersion, iMinorVersion;
if (!eglInitialize(eglDisplay, &iMajorVersion, &iMinorVersion))
{
User::Leave(KErrExtensionNotSupported);
}
EGLint pi32ConfigAttribs[3];
pi32ConfigAttribs[0] = EGL_SURFACE_TYPE;
pi32ConfigAttribs[1] = EGL_WINDOW_BIT;
pi32ConfigAttribs[2] = EGL_NONE;
int iConfigs;
if (!eglChooseConfig(eglDisplay, pi32ConfigAttribs, &eglConfig, 1, &iConfigs) || (iConfigs != 1))
{
User::Leave(KErrExtensionNotSupported);
}
eglSurface = eglCreateWindowSurface(eglDisplay, eglConfig, (NativeWindowType)DrawableWindow(), NULL);
if (eglGetError() != EGL_SUCCESS)
{
User::Leave(KErrExtensionNotSupported);
}
eglContext = eglCreateContext(eglDisplay, eglConfig, NULL, NULL);
if (eglGetError() != EGL_SUCCESS)
{
User::Leave(KErrExtensionNotSupported);
}
eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext);
if (eglGetError() != EGL_SUCCESS)
{
User::Leave(KErrExtensionNotSupported);
}- Create a Texture Manager to Load and Store maps, we will see this class after
iTxtManager = CCTextureManager::NewL();- Create our CActivePositionner
iPositioner = new CCActivePositioner(*this);
iPositioner->ConstructL();- Calc the iWidth and iWidth value for a Maps, according the KPosTL and KPosBR constants
TPosition origin;
origin.SetCoordinate(KPosTL.Latitude(),KPosBR.Longitude());
origin.Distance(KPosBR,iWidth);
origin.Distance(KPosTL,iHeight);- And finally create a PeriodicTImer which will call a 3D Specific method for Draw, in OpenGLES we can't use the standard Draw method so we need to do this, here we choose to redraw every 100ms ( 10 time / seconds )
iPeriodic = CPeriodic::NewL(CActive::EPriorityIdle);
iPeriodic->Start(100000,10000,TCallBack(CMapGpsAppView::DrawCallback,this));
}Perhaps the OpenGLES initialisation seems a bit complicated but just look at some OpenGL tutorial and you'll see it's quite simple.
Next methods the SetPos :
void CMapGpsAppView::SetPos(TPositionInfo aInfo)
{
aInfo.GetPosition(iPos);
TPosition origin;
origin.SetCoordinate(KPosTL.Latitude(),iPos.Longitude());
TReal32 distX;
TReal32 distY;
origin.Distance(iPos,distX);
origin.Distance(KPosTL,distY);
TReal32 idX = distX/iWidth;
TReal32 idY = distY/iHeight;
iXIdx = (TInt)idX;
iYIdx = (TInt)idY;
iDeltaX = distX - (iWidth*(TReal32)iXIdx);
iDeltaY = distY - (iHeight*(TReal32)iYIdx);
iDeltaX = iDeltaX/iWidth;
iDeltaY = iDeltaY/iHeight;
}Here we just calc the distance along x and y axes form our location and the Top Left Position of the (0,0) maps, and divise them by the iWidth and iHeight size of a maps. The rest of this division will give the iDeltaX ans iDeltaY value
- Last important method, the Draw methods
void CMapGpsAppView::MyDraw( )
{
// Clears the screen
glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
for (int x = -1;x<=1;x++)
for (int y = -1;y<=1;y++)
{
GLfloat pfVertices[] = {
x-0.5f+iDeltaX,y-0.5f+iDeltaY, 0.0f,
x+0.5f+iDeltaX,y-0.5f+iDeltaY, 0.0f,
x+0.5f+iDeltaX,y+0.5f+iDeltaY, 0.0f,
x+0.5f+iDeltaX,y+0.5f+iDeltaY, 0.0f,
x-0.5f+iDeltaX,y+0.5f+iDeltaY, 0.0f,
x-0.5f+iDeltaX,y-0.5f+iDeltaY, 0.0f };
// Enable vertex arrays
glEnableClientState(GL_VERTEX_ARRAY);
// Points the the vertex data
glVertexPointer(3,GL_FLOAT,0,pfVertices);
TBuf<64> str;
str.Format(KText,iXIdx+x,iYIdx+y);
// Pass the texture coordinates data
GLfloat afTexCoord[] = {0.0f/*+deltax*/, 0.0f/*+deltay*/,
1.0f/*+deltax*/, 0.0f/*+deltay*/,
1.0f/*+deltax*/, 1.0f/*+deltay*/,
1.0f/*+deltax*/, 1.0f/*+deltay*/,
0.0f/*+deltax*/, 1.0f/*+deltay*/,
0.0f/*+deltax*/, 0.0f/*+deltay*/};
if (iTxtManager->SelectTexture(str) == KErrNone)
{
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glTexCoordPointer(2,GL_FLOAT,0,afTexCoord);
}
glDrawArrays(GL_TRIANGLES, 0, 6);
}
eglSwapBuffers(eglDisplay, eglSurface);
}Don't be afraid by this methods, it just :
- Clear the screen with glClear
- Start a Loop to Draw the central map and the one which are adjacent, so the "for (int x = -1;x<=1;x++) and for (int y = -1;y<=1;y++) "
- Declare 2 triangles to make a square in pfVertices, which are decaled by the iDeltaX and iDeltaY value calc in SetPos to make our position be in the center of the screen
- Format a string to get a the good maps with "str.Format"
- Declare some texture coordinate to draw the maps on the square in afTexCoord
- Load and Select the maps to bedrawn with iTxtManager->SelectTexture
- If the maps is Loaded Draw it with glEnableClientState(GL_TEXTURE_COORD_ARRAY) and glTexCoordPointer(2,GL_FLOAT,0,afTexCoord);
- Finally draw this with glDrawArrays and put that on the screen with eglSwapBuffers
That's all important methods for the CMapGpsAppView class.
Now just have a look on the CTextureManager class. It has an Array In which it save the images it has already loaded and when we ask it for an image, it first parse the array to find the image, and select it for OpenGLES if founded. If non founded the image is added in the array and then loaded.
The Array is CPointerArray which contains some CTexture object which are defined like that :
class CTexture
{
public:
CTexture() :iFileName(0) ,iBuff(NULL), iBuffSize(0),iGlIndex(0xFFFF) {}
~CTexture() {delete iBuff; }
public:
TInt CreateCurrentOglTexture(); //transform the CFbsBitmap in Pixel Array for OpenGLES
TBuf<64> iFileName; // path of the texture
unsigned char* iBuff; // Pixels array for OpenGLES
TInt iBuffSize; // size of pixel array
TUint iGlIndex; // OpenGLES index to select the texture
TInt iX; // width in pixel of the texture
TInt iY;// Height in pixel of the texture
TInt iBpp; //bit per pixel for the texture, should be 3
CFbsBitmap* iBitmap; //Bitmap which is set with the image by CImageDecoder
};CTextureManager is an ActiveObject which use the CImageDecoder to load the image.
There is two important method for this class :
First the SelectTexture which look for the image and add it in the array if not founded :
TInt CCTextureManager::SelectTexture(TDesC& szFilename)
{
CTexture* txt;
TBool found = false;
//first search texture in the array
for (int i=0;i<iArray.Count();i++)
{
txt = iArray[i];
if ( txt && (szFilename == (txt->iFileName)) )
{
if (txt->iGlIndex != 0xFFFF)
{
//do OGL stuff and return
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, txt->iGlIndex);
return KErrNone;
}
else
{
found = true;
}
}
}
if (!found)
{
//the File is not in the array so Add It
txt = new CTexture;
txt->iFileName = szFilename;
iArray.Append(txt);
//if needed (re)Launch the Active Object
if ( !IsActive())
{
iState = EReady;
TRequestStatus* s = &iStatus;
User::RequestComplete(s,KErrNone);
SetActive();
glDisable(GL_TEXTURE_2D);
}
}
return KErrNotReady;
}And the RunL methods which Get the image decoded with CImageDecoder and transform it into OpenGLES Format and Lauch another Image Decoding if there is pending image in the array:
void CCTextureManager::RunL()
{
if (iState == EBusy)
{
switch (iStatus.Int())
{
case KErrUnderflow:
{
//need to continue conversion
iDecoder->ContinueConvert(&iStatus);
SetActive();
return;
}
case KErrNone:
{
//image is Decoded do OglStuff to create Texture
if (NULL != iArray[iDecoded])
{
iArray[iDecoded]->CreateCurrentOglTexture();
}
iDecoded++;
iDecoder->Cancel();
delete iDecoder;
iDecoder = NULL;
iState = EReady;
TRequestStatus* s = &iStatus;
User::RequestComplete(s,KErrNone);
SetActive();
break;
}
default:
//img not decoded must be an error let the CTexture empty
{
iDecoded++;
iDecoder->Cancel();
delete iDecoder;
iDecoder = NULL;
iState = EReady;
TRequestStatus* s = &iStatus;
User::RequestComplete(s,KErrNone);
SetActive();
}
}
}
else if (iStatus.Int() == KErrNone)
{
//see if we have texture pending
if (iDecoded != iArray.Count() )
{
CTexture* txt = iArray[iDecoded];
//start decode
RFs& fs = CCoeEnv::Static()->FsSession();
fs.Connect();
//test if exist
RFile file;
if (file.Open(fs,txt->iFileName,EFileRead) != KErrNone)
{
iDecoded++;
TRequestStatus* s = &iStatus;
User::RequestComplete(s,KErrNone);
SetActive();
return;
}
file.Close();
iDecoder= CImageDecoder::FileNewL(fs,txt->iFileName,KMime);
const TFrameInfo &frameInfo = iDecoder->FrameInfo(0);
txt->iBitmap = new CFbsBitmap;
txt->iBitmap->Create(frameInfo.iOverallSizeInPixels, frameInfo.iFrameDisplayMode );
iDecoder->Convert(&iStatus,*txt->iBitmap);
iState = EBusy;
SetActive();
}
}
}Ok that's All
I hope this tutorial is still clear and that the 3D drawing don't not confuse you too much
But I really thinks that 2D drawing, with gc.BitBlt methods wouldn't have easier to understant because we would have take the maps pixels resolution into account and it's not simple, here we don't care, because image are just drawn on a square.
--
MarCo
Oups while reading this tutorial I saw I'll told you to use 3D because it's easier for zoom managing and I did not use it, so if you want to manage zoom juste add a variable, TReal iZoom, for example, make it change with touch with -4
for (int x = (int)(iZoom);x<=(int)(-iZoom);x++)
for (int y = (int)(iZoom);y<=(int)(-iZoom);y++)






Re: Playing with the N95 - Use LBS API and so more
i'm sorry i've tried to follow this tutorail and create an app that displays a map and your location on it but i can't seem to get it working
i know code is included in the tutorial but i'm not clear where it's all supposed to go?
i'm not sure if i'm the only one to try this or if i'm just missing something lol i would be grateful for any help.
cheers
Re: Playing with the N95 - Use LBS API and so more
Hello
did you Add the Location capabilites ?
and sign the sis ?
--
MarCo
Re: Playing with the N95 - Use LBS API and so more
Yeah i've been through the first tutorial but i'm having a few problems with this one, really all i want is an app that i can put a map in and have the gps location on aswell then i'm looking to add more modifications to it to make it more usable.
Re: Playing with the N95 - Use LBS API and so more
This article is juste a Description for using the LBS API
it's not dedicated to be a real App
--
MarCo
Re: Playing with the N95 - Use LBS API and so more
cheers for the reply
cheers well i'll have another digg around i'll figure it out