Home > Android, Maps > Fuchs Maps

Fuchs Maps

Sometime ago after I finished my tutorials for Android offline map app I spent some time adding more features to the simple app, I guess I should share the code since many of those features are expected from any map application.

The post here won’t contain detailed explanation about how each feature is implemented since each feature might require it’s own post. Anyway if you’ve been reading my previous tutorials everything here should be straightforward and easy to understand, consider the source code provided here as some sort of raw data that someone might benefit from 🙂 .

This code is built upon the offline version of the app, so it doesn’t have web tiles support and the mapView cannot be created in xml (these can be changed easily by following the previous two tutorials).

The code isn’t perfect either, it does the job though 🙂 .

Here’s the features list:

The post will contain lots of images so please be cautious :).



Points of Interest:


Points of interest

Points of interest
(in green)



These are points that can be created/modified at runtime, just by long touching the map a button with options will appear.

Each point when touched will create a toast showing it’s name.

com.fuchs.poi/ POI:
Each poi has an Id (for database storage), name, gps coordinates, and a visibility flag.

com.fuchs.poi/ POIProvider:
class with static methods used to get\insert\modify points in the database.

com.fuchs.maps.views.overlays/ MapOverlay
A MapOverlay is a layer drawn on the mapView after the mapView has done rendering the tiles.

The class mapView now contains a list of MapOverlay objects, a MapOverlay is a simple class that gets notified when changes occur to the mapView it is connected to.

com.fuchs.maps.views.overlays/ POIOverlay
This class contains a list of POI objects called points (added using POIOverlay.addPoint), now in order to draw each point at the right location we need to convert the gps coordinates of the point to map pixel coordinates which is done by calling tilesManager.lonLatToPixelXY , to avoid calling this function on each draw, we call it once per zoom level and save the result for later use in the list absPoints.
If the zoom level of the mapView changes then we’ll have to recalculate the pixel coordinates.

GPS Tracking Service:


Two different tracks

Two different tracks



With this feature you can track and record your location and view it while recording or later after the recording is done.

The recording is done using a service, so if you start it and put the app in the background it will still be running.

Tracks are rendered using an overlay called TracksOverlay.

com.fuchs.tracking/ Track
A Track is simply a list of TrackPoint objects along with an id and a visibility flag.

com.fuchs.tracking/ TrackPoints
Each TrackPoint contains gps coordinates and the time it was recorded at.

com.fuchs.tracking/ TracksProvider:
Contains some static methods to handle creating/modifying tracks in the tracks database.

com.fuchs.maps.views.overlays/ TracksOverlay:
To draw a track we simply convert its TrackPoints to map pixel coordinates and then draw a line between each pair of points, point i and point i+1 where i goes from 0 to number of points -2.

Just for optimization we calculate the pixel coordinates for each point and store it in an instance of TracksOverlay.AbsTrack which can later be used for rendering.

com.fuchs.tracking/ TrackerService:
The service when started creates a LocationListener which updates the static Track object in the TrackerService whenever a new location is available.

Compass:

For the compass I modified the code here a little bit and put it in com.fuchs.util.CompassSensor.java, the class takes a Handler in the constructor, this handler will be sent a message whenever a new value is available.

To draw the compass I added a drawable of a compass to the layout, then I rotated the image of the compass according to the values passed to the Handler.

This Handler can be found in the main activity FMapsActivity and it’s called compassHandler.

Location Sharing:

When you long-touch the map a button will appear and you can choose to share the location of the place you touched, the app will show a list of apps you can share the location with, the format of the shared coordinates as simply latitude,longitude , you can change the format as you like.

FMaps_btnOptions

FMaps_btnOptionsMenu

The sharing is done in FMapsActivity at btnShareLocationOnClick.

One thing I should mention here is the button that appears on the map, the FMapsActivity implements the IMapViewEventListener interface which contains the method onLongPress, now when you long-press the mapView this will notify the activity and it will show the button called btnOptions and set the coordinates of the pressed point in the tag of btnOptions.

Map selection at runtime:

This can be done by pressing the menu button and selecting Maps.

You must make sure that the folder name of the maps is valid, this folder name can be changed in the settings.
By default the app expects to have a folder named something like this /mnt/sdcard/Fuchs Maps/ that contains the maps. Please make sure the path contains the last slash.

Changing tiles size at runtime:

This can be done in the settings, the tiles size is passed to both MapView and TilesManager.

The smaller the tile size is the more tiles can be fit in the view which in turn will result in more memory usage and of course rendering will take more time.

Click to enlargeWarning: 585KB :DLeft: 256 (100%)Center: 512 (200%)Right: 128 (50%)

Click to enlarge
Warning: 585KB 😀
Left: 256 (100%)
Center: 512 (200%)
Right: 128 (50%)

You can use filtering to make the tiles look smoother when you select a tile size larger than the actual size of the tile image, just uncomment the line bitmapPaint.setFilterBitmap(true); in the constructor of MapView, note that enabling filtering will affect performance.

Importing\Exporting points and tracks:

Since the classes Track, TrackPoint, POI are all serializable we can write them into files, the export/import functionality is in both TracksProvider class and POIProvider class.

_____________________________________________

Source Code:

FuchsMaps.zip (176KB) – Eclipse project, Android 2.2+

If you want something explained more please use the comments bellow, if you’re new at this please read the previous tutorials or none of the above will make sense.

One final note: try adding features to your existing app and don’t try to add the features of your app here, it’s easier and better to use your own code design instead of trying to understand a full project like this one.

Categories: Android, Maps Tags: , ,
  1. noor
    06/03/2013 at 12:32 am

    عمل رائع

    • Fuchs
      06/03/2013 at 1:03 am

      شكراً جزيلاً
      Thanks a lot.

  2. Federico
    08/03/2013 at 1:35 am

    Hi man, thaks a lot for the whole tutorial. It was really helpfull for a school proyect i had to do. Thanks to you, I made my own map application, which includes my own geoServer, and the objective of the app is to draw a bus route for the public transport and include the schedule. I couldn’t analize the new features you included yet, i already have most of them in my proyect.

    Let me ask you, why is the restriction of Android 2.2+ ?
    Thanks you very mach again

    • Fuchs
      08/03/2013 at 10:24 am

      Glad I could help :D, the API level restriction wasn’t intentional, I chose API 8 since I thought it’s low enough.
      Now it seems that I could have easily chosen API 7 or even lower! thanks for letting me know :).

  3. lutfi
    31/03/2013 at 11:50 am

    hi Fuchs,
    i hope you are fine.
    i made your tutorials my starting point.thank you very much.
    i have a serious problem. this may be easy for you.
    can i access from locationdataoverlay class to the textview defined inside the main-xml file at fuchsmappactivity class (main app).

    i had got null pointer exception or class cannot cast exception .

    i made your program xml supported as you helped me before.
    thank you very much again.
    regards.

    • Fuchs
      31/03/2013 at 9:31 pm

      Hi Lutfi, I’m fine thanks 🙂
      I see that you’re trying to display location data in a TextView other than using canvas.drawText am I right?

      So you’ve added a TextView to the file main.xml and want to access it from LocationDataOverlay, I wonder why it isn’t working for you, I mean if you can access it from the activity you should be able to access it from there.

      In my code the file main.xml is inflated at the beginning of onResume of FMapsActivity

      @Override
      protected void onResume()
      {
      	setContentView(R.layout.main);
      	super.onResume();
      
      	display = getWindowManager().getDefaultDisplay();
      
      	initViews(); // <--- use findViewById here to get your TextView
      	StateManager.restoreMapViewPreferences(this, mapView);
      
      	initOverlays(); // <--- LocationDataOverlay created here
      
      ...
      

      Overlays are created after the layout is inflated and the views references are retrieved.
      I cannot help you much without seeing your xml file and some code, make sure you can access your TextView inside the activity.
      Please let me know of the results.

      • lutfi
        01/04/2013 at 10:22 am

        hi fuchs,

        	@Override
        	protected void onResume()
        	{
        		
        		
        		initViews();
        		
        		// Restore zoom and location data for the MapView
        		StateManager.restoreMapViewPreferences(this, mapView);
        		
        		initOverlays();
        		
        		// Creating and registering the location listener
        		locationListener = new MapViewLocationListener(mapView);
        		LocationManager locationManager = (LocationManager) getSystemService(LOCATION_SERVICE);
        		locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, locationListener);
        
        	     
        		frm = new FrameLayout(this);
        		
        		
        		ImageButton b1 = new ImageButton(this);
        		b1.setBackgroundResource(R.drawable.eksi_bt);
        		FrameLayout.LayoutParams peksi = new FrameLayout.LayoutParams(  
        	            FrameLayout.LayoutParams.WRAP_CONTENT,  
        	            FrameLayout.LayoutParams.WRAP_CONTENT);   
        	            peksi.topMargin = 0;  
        	            peksi.gravity = Gravity.RIGHT | Gravity.CENTER_VERTICAL;  
        	    b1.setLayoutParams(peksi);
        	    b1.setOnClickListener(new View.OnClickListener()
        	    {
        	        public void onClick(View v)
        	        {
        	            mapView.zoomOut();
        	        }
        	    });
        	    
        	    
        		ImageButton b2 = new ImageButton(this);
        		b2.setBackgroundResource(R.drawable.arti_bt);
        	    FrameLayout.LayoutParams parti = new FrameLayout.LayoutParams(  
        	            FrameLayout.LayoutParams.WRAP_CONTENT,  
        	            FrameLayout.LayoutParams.WRAP_CONTENT);   
        	            parti.topMargin = 0;  
        	            parti.gravity = Gravity.RIGHT  | Gravity.BOTTOM;  
        	    b2.setLayoutParams(parti);
        	    b2.setOnClickListener(new View.OnClickListener()
        	    {
        	        public void onClick(View v)
        	        {
        	            mapView.zoomIn();
        	        }
        	    });
        
        	    
        	   
        
        	    
        	    header = (View)getLayoutInflater().inflate(R.layout.haritaust_lay, null);
        	    FrameLayout.LayoutParams pheader = new FrameLayout.LayoutParams(  
        	            FrameLayout.LayoutParams.MATCH_PARENT,  
        	            FrameLayout.LayoutParams.MATCH_PARENT);   
        	            pheader.topMargin = 0;  
        	            pheader.gravity = Gravity.TOP ;  
        	    header.setLayoutParams(pheader);
        	    	    
        	    View footer = (View)getLayoutInflater().inflate(R.layout.haritaalt_lay, null);
        	    FrameLayout.LayoutParams pfooter = new FrameLayout.LayoutParams(  
        	            FrameLayout.LayoutParams.MATCH_PARENT,  
        	            FrameLayout.LayoutParams.MATCH_PARENT);   
        	            pfooter.height = 20 ; 
        	            
        	            pfooter.gravity = Gravity.BOTTOM ;  
        	    footer.setLayoutParams(pfooter);
        	        
        	    frm.addView(mapView);
        	    frm.addView(b1);
        	    frm.addView(b2);
        	   
        	    frm.addView(header);
        	    frm.addView(footer);
        	    setContentView(frm);
        
        	    ImageButton b3 = (ImageButton) findViewById(R.id.haritaust_bt);
        		
        		   
        	    b3.setOnClickListener(new View.OnClickListener()
        	    {
        	        public void onClick(View v)
        	        {
        	        	cabukmenugoster(v);
        	        }
        	    });
        	    
        	    
        	    cabuktepki();
        	    
        	   		
        		super.onResume();
        	}
        
        <?xml version="1.0" encoding="utf-8"?>
        <RelativeLayout 
            android:orientation="horizontal" 
            android:background="@drawable/haritaustzemin" 
            android:layout_width="fill_parent" 
            android:layout_height="wrap_content"
          xmlns:android="http://schemas.android.com/apk/res/android">
            <RelativeLayout 
                android:layout_width="fill_parent" 
                android:id = "@+id/deneme"
                android:layout_height="wrap_content">
                <TextView 
                    android:textSize="12.0sp" 
                    android:textColor="@color/mavi" 
                    android:ellipsize="end" 
                    android:gravity="center_horizontal" 
                    android:layout_gravity="left" 
                    android:id="@id/haritaalt_tv1" 
                    android:background="@null" 
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content" 
                    android:layout_marginLeft="5.0dip" 
                    
                    android:shadowColor="@color/beyaz" 
                    android:shadowDx="0.3" 
                    android:shadowDy="0.3" 
                    android:shadowRadius="1.0" 
                    android:layout_alignParentLeft="true" />
                <LinearLayout 
                    android:layout_width="wrap_content" 
                    android:layout_height="wrap_content" 
                    android:layout_alignParentRight="true">
                    
                    
                    <TextView 
                        android:textSize="12.0sp" 
                        android:textColor="@color/mavi" 
                        android:layout_gravity="center" 
                        android:id="@id/haritaalt_tv2" 
                        android:layout_width="wrap_content" 
                        android:layout_height="wrap_content" 
                        android:layout_marginLeft="5.0dip" 
                        android:layout_marginRight="5.0dip" 
                         />
                </LinearLayout>
            </RelativeLayout>
        </RelativeLayout>
        

        the first part from main actvity.
        the second is from the footer added to frame view which have textview fields i want to access from overlay.

        in locationdataoverlay i tried to put the data as:

        TextView har = (TextView) findViewById(R.id.haritaalt_tv1);
        		
        		har.setText("zoom ---" + izoom);
        

        i hope the codes are clear enough.

        thank you very much.

        • Fuchs
          01/04/2013 at 2:43 pm

          Okay can you try putting

          TextView har = (TextView) findViewById(R.id.haritaalt_tv1);

          Just after

          setContentView(frm);

          and put a breakpoint after that and see if the TextView har will be null or not, if it’s not null try creating the overlays after initializing your views. Try also calling header.findViewById.

          I wonder how you’re calling findViewById inside LocationDataOverlay which doesn’t have any Context object attached.

          You can also try using handlers, define a Handler in the map activity and make it update the text view when it receives a message, the LocationDataOverlay can then call FMapsActivity.locationHandler.sendMessage to send a message containing the data you want to display.

          • lutfi
            01/04/2013 at 6:12 pm

            thank you very much for your reply.
            i tried all of the things except handler. handler is not suitable for this because if i use at main activity there is no problem.
            there is no similar situation on the stackoverflow community . it is unbelievable that this simple problem gets complicated.
            i found similar example on robertmaps poioverlay class. but this was also not as i want. the footer completely different instance of the original.
            anyway i will continue to use your method it can be accessed to the bottom of the screen by means of mapview dimensions.
            no problem.
            thank you very much.
            best wishes.

  4. Riya
    16/04/2013 at 1:48 pm

    Thank you very much for this tutorial.
    But i am facing this problem “Error creating tiles provider” on opening the application in device.
    Please help.

    • Fuchs
      16/04/2013 at 7:01 pm

      That’s because you don’t have a valid maps folder set in the app settings, to solve this : Menu->Settings->Maps folder then type the path to the folder where you have the map file, you can download a sample file from here:
      World.sqlitedb 1,580KB
      or from here
      World.sqlitedb 1,580KB

      The map file is created using Mobile Atlas Creator in RMaps format.
      After you download the map file and select the path to the map file simply press Menu->Maps and all the maps available in the specified folder will be displayed in a list.
      I hope this helps :).

      • Riya
        01/05/2013 at 11:18 am

        Hi, I’m still facing the same problem.

        • Fuchs
          02/05/2013 at 5:20 am

          Is the map file shown in the list when you click Menu->Maps?

          • Sunny Chew
            26/05/2013 at 1:08 pm

            Hi, Fuchs.
            Now I able to display the map I want at specific zoom level.
            But I facing the same problem as stated above that when I export and install the FuchsMaps.apk in my android-power device, it show “Error creating tiles provider” problem.
            I did check the path of map and the path is correct because map file shown the map inside successfully.
            When I click the RMaps, it still show the error that I mentioned.
            Can you tell me how to fix it?
            Thanks

          • Fuchs
            28/05/2013 at 12:34 pm

            Hi, I’m not sure if I got this right: so my database file worked for you but when you tried a database you exported it didn’t work?
            Can you please post the LogCat (or the part of it you see relevant)?

      • Quasser
        03/07/2014 at 5:56 am

        what do you mean by “Menu->Settings->Maps folder”? we’re can i find that? i dont get it at all

  5. Sunny Chew
    10/05/2013 at 12:45 pm

    Hi, Fuchs.
    I want the app to display the map of my city start from zoom level 11 – 15. I used MOBAC to export the map in RMaps Format and replace it with world.sqlitedb. But the apps display grey area. I’m a beginner here and I try to find the solution from the tutorials and previous comments but it makes me more confused. Can you help me?
    Thanks in advanced.

    • Fuchs
      13/05/2013 at 4:47 pm

      Hi, I guess you’ve already read this comment.
      One solution is to manually get the coordinates of the city where you have tiles and set it as the default starting location.
      To get the coordinates simply go to https://maps.google.com/ and right click on the city then select “What’s here?” and it will return the location in the search box as latitude, longitude.

      And sorry for the late response but it’s kind of exams period now 🙂

  6. Sunny Chew
    31/05/2013 at 1:01 am

    Hi, so this is what I get from LogCat

    05-30 22:44:28.821: D/dalvikvm(130): GC_EXTERNAL_ALLOC freed 1521 objects / 95472 bytes in 1108ms
    05-30 22:44:45.170: I/ActivityManager(63): Starting activity: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.fuchs.maps/.FMapsActivity }
    05-30 22:44:45.560: I/ARMAssembler(63): generated scanline__00000077:03545404_00000004_00000000 [ 47 ipp] (67 ins) at [0x34e940:0x34ea4c] in 21277192 ns
    05-30 22:44:48.841: D/dalvikvm(136): GC_EXPLICIT freed 1266 objects / 90184 bytes in 24435ms
    05-30 22:44:55.190: W/ActivityManager(63): Launch timeout has expired, giving up wake lock!
    05-30 22:44:55.258: W/ActivityManager(63): Activity idle timeout for HistoryRecord{43efdea0 com.fuchs.maps/.FMapsActivity}
    05-30 22:45:06.791: D/dalvikvm(130): GC_EXPLICIT freed 193 objects / 9336 bytes in 1596ms
    05-30 22:45:50.106: W/WindowManager(63): No window to dispatch pointer action 0

    is this the Information you need? The error occur when I launching the app by selecting Run as, However everything runs well as launching the app by selecting Debug as. Any differ with that?
    Thanks.

    • Fuchs
      01/06/2013 at 10:13 pm

      Thanks for posting the LogCat but I can’t find it useful, there’s nothing related to the database.
      Since you can’t debug when running (obviously 😀 ) I suggest printing a custom message in the LogCat, just right before creating the TilesProvider.
      This way you can see the error message more clearly.

      This debug vs run issue is really weird though!

  7. Nishadh
    07/06/2013 at 10:27 am

    Thank you very much for the app and great tutorial.

    • Fuchs
      07/06/2013 at 8:11 pm

      You’re welcome :).

  8. Nguyen Hang Nga
    19/06/2013 at 12:14 pm

    Hi Fuchs,
    I just figured out that your code need to modify a bit to get GPS signal for tracking option.
    For example, if I just stay at some static place, then when I start tracking, no location will be detected by GPS.
    Therefore, I just follow this advice and add a small code and it works like a charm:))
    Source:
    http://stackoverflow.com/questions/9873190/my-current-location-always-returns-null-how-can-i-fix-this

    • Fuchs
      19/06/2013 at 7:44 pm

      Hi Nguyen,
      That’s actually strange! Does it return null even when you start moving? I’m asking you this because onLocationChanged will only be called when your location is changed by at least a predefined step MyPreferences.getTrackStep.

      I’m not using getLastKnownLocation when the tracking service starts, so you have to move a little bit for the TrackerService to store a value when started.

      You can also set the step to zero from the app settings and then the service should store values even without moving.

      You just have to make sure your phone can get a GPS fix by trying other apps like the default Google Maps app.

      Thanks for the feedback 🙂

  9. Nguyen Hang Nga
    21/06/2013 at 3:21 am

    HI Fuchs,

    Actually, 2 days ago, I had tested your app when I was travelling in MRT. And even I was at 21.85 Km aways from the starting point, the location hadn’t updated anything and no path was recorded (it is supposed to have a line path to be drawed, right?). Does it implied the onLocationChanged() hadn’t been called?

    Best regards,

    • Fuchs
      21/06/2013 at 3:42 am

      Hi Nguyen,

      GPS functions poorly when underground, is that the case? did you check other map apps?
      The current track should be drawn in red as it’s being recorded.

      If you’re not going underground and\or other apps can get a GPS fix then something is definitely wrong with my code :).
      What Android version are you running? I developed\tested the app on API10, could it be that something about Services has changed in the newer APIs?

  10. Nguyen Hang Nga
    21/06/2013 at 5:04 am

    Hi Fuchs,

    I am running at API 17.
    I will also try to test with another app after my office hour today, then will notify you later:))
    Anyway, thank you so much for your help and your immediate replies.
    It would be perfect to make friend with you 🙂
    Best regards,

    • Fuchs
      21/06/2013 at 4:12 pm

      I’m happy to help 🙂

  11. Nguyen Hang Nga
    21/06/2013 at 7:16 am

    Hi Fuchs,
    I just used another app to fake my GPS signal and everything work perfectly.
    Therefore, there must be a problem with my GPS or smt need to be changed to get stable GPS signal in code. I will double check, if anything wrong and I am able to fix it, will notify you later:)
    Anyway, really thankful for your great tutorials. And, is it possible if I take a portion of your code in my school app?

    • Fuchs
      21/06/2013 at 4:10 pm

      Hi Nguyen,
      I hope you get it fixed 🙂
      About using my code: first, thanks for asking, that’s really polite.
      Second, you can use whatever you want, I should have specified a license for the code to allow that explicitly, maybe next project 😉
      Good luck with your project!

  12. E.F. Nijboer
    11/10/2013 at 3:41 pm

    The reason why I got “Error creating tiles provider” was that when typing the setting for the Maps Folder I did not add a trailing ‘/’. Selecting the Map File will give no problems because it will show the files in the directory. The application however does not add the trailing slash (‘/’).

    I guess the code of getFullMapFile should be something like this to fix it:
    return new java.io.File(getMapFolder(context), getMapFile(context)).getPath();

    • Fuchs
      13/10/2013 at 9:41 pm

      Ops! looks like I missed that one. Thanks! 🙂 .

  13. 27/01/2014 at 6:41 pm

    hi…hope u will be fine i m trying to run ur apk file but it show me blank screen with zoom in and out button ,but map not show i think i m not placing map file in right place above i read to click men->setting->map-folder i m facing difficulty here where is the menu which i have to click ….in the last taturial i just have to make a folder mapaapp and put file of sqlite in it then simply,but work this time not plz clear me where i should keep the map file in device

    • Fuchs
      28/01/2014 at 7:21 pm

      Hi Zafar, I guess your device doesn’t have a hard Menu button and my code is kind of old now and I didn’t support this.
      You can open the source code of the project and hardcode the database path (for now).
      In FMapsActivity.java find the method initViews() and put the path to the database there.
      tilesProvider = new TilesProvider(MyPreferences.getFullMapFile(this));

  14. Altaf
    20/02/2014 at 11:30 am

    Hi,
    This is very nice tutorial and i used it in my offline application and this is working fine. How i can add arrow line between two points? and i don’t want to delete previous marker, in my case it is deleted each time i add new marker when i call mapview.invalidate();
    Thanks.

    • Fuchs
      22/02/2014 at 11:44 am

      Hi, you’ll have to save the line/marker in some variable and draw it each time you call mapview.invalidate()

      • Altaf
        03/03/2014 at 8:19 am

        means i need to put marker in list each time location will change and draw all marker every time location changes?

        • Fuchs
          03/03/2014 at 5:29 pm

          You’ll need to redraw the the map view each time something changes. This includes camera changes, markers changes and any other changes that should have an effect on the map.

          You need to save your markers and draw them each time along with the map tiles.

  15. 17/04/2014 at 2:34 pm

    Hi, this tutorial is great. thanks a lot for that as I am new to developing android apps and going straight to do my first one with a map in it and that too offline.

    I also came through the problem of “Error creating tiles provider”, but solved it (dont really know what worked, but its not giving that error now at least) but I am now facing errors ‘java.lang.NullPointerException’ at com.fuchs.maps.StateManager.onSaveInstanceState() whenever I choose any option from the Menu, such as the left arrows at the end of each track in the Tracks menu item.

    • Fuchs
      20/04/2014 at 8:29 pm

      You can place a break point there and run the code step by step till find the cause.
      Sorry if I’m not very helpful now but I don’t have much time recently.

  16. Kai
    01/02/2016 at 9:46 pm

    Hi Fuchs,
    This is very nice tutorial and how to add a search feature to find place, address or building names.
    Thanks

  1. 05/07/2013 at 4:39 am

What do you think?