Facebook Twitter LinkedIn E-mail
magnify
Home 2011 一月

Windows Mobile引路蜂地图开发示例:准备开始

在正式介绍Windows Mobile引路蜂地图开发示例前,先说明一下开发环境和要些准备工作。Windows Mobile 6.x引路蜂地图开发示例采用Visual Studio 2008为开发环境,为了达到类库最大兼容性,引路蜂地图开发包是基于.Net Framework 2.0开发的,所以类库可以用于2.0以上.Net Framework (包括Desktop 和 Compact Framework)。

与Android, Java ME平台类似,需要提供Gis.Drawing 的平台相关实现和合法的使用许可才可以使用引路蜂地图开发包。

1.  引路蜂开发包是以.Net Framework类库形式提供的,需在项目中添加对这些类库的引用。其中Mapdigit.Drawing ,Mapdigit.Gis, Mapdigit.Licence, Mapdigit.Util 是可以在程序中使用的,Mapdigit.Ajax, Mapdigit.Crypto ,Mapdigit.Rms, Mapdigit.Network 为辅助类库并经过扰码(Dotfuscated)无法再程序中使用。

2. NetFont ,NetGraphics ,NetGraphicsFactory ,NetImage 为Gis.Drawing 在Windows Mobile 上的实现,以源码方式提供。

3. guidebee.lic 为使用许可文件,必须有合法的使用许可地图应用才可以正常运行。

 

Windows Mobile引路蜂地图开发示例:二维图形库

Windows Mobile引路蜂地图开发包带有一个高效二维图形库,这是因为诸如LineCap, LineJoin, Brush, TextBrush, Path 等方法在Windows .Net Compact Framework 平台上不支持。Windows Mobile引路蜂地图开发包中的二维图形库弥补了这些缺陷,它提供了在桌面平台System.Drawing.Drawing2D中相应功能。 

 

引路蜂地图开发包使用了这个图形库来绘制路径以及矢量地图。 

基本知识 

二维图形库使用类Graphics2D作为画板,内部画板实际为一个整数型二维数组。这种设计可以实现平台无关性。在绘制好图形后,最终是要在屏幕上显示的。在Windows Mobile平台上没有提供直接绘制整数型二维数组的方法。下面是在Windows Mobile绘制数型二维数组的方法。数组中每个元素为一个32位整数,格式为 AAAARRRRGGGGBBBB,分别代表透明度,红色,绿色,蓝色。 

/// <summary>
/// Graphics 2D Object
/// </summary>
private readonly Graphics2D graphics2D; 

/// <summary>
/// screen width
/// </summary>
private readonly int screenWidth; 

/// <summary>
/// screen Height
/// </summary>
private readonly int screenHeight;
.....
screenWidth = Width;
screenHeight = Height;
graphics2D = new Graphics2D(screenWidth, screenHeight);
private void MainForm_Paint(object sender, PaintEventArgs e)
{
    DrawRGB(e.Graphics, graphics2D.Argb, 0, 0, screenWidth, screenHeight);
}

////////////////////////////////////////////////////////////////////////////
//--------------------------------- REVISIONS ------------------------------
// Date       Name                 Tracking #         Description
// ---------  -------------------  -------------      ----------------------
// 24SEP2010  James Shen                               Code review
////////////////////////////////////////////////////////////////////////////
/// <summary>
/// Draws the RGB.
/// </summary>
/// <param name="graphics">The graphics.</param>
/// <param name="rgbData">The RGB data.</param>
/// <param name="x">The x.</param>
/// <param name="y">The y.</param>
/// <param name="w">The w.</param>
/// <param name="h">The h.</param>
private static void DrawRGB(Graphics graphics, int[] rgbData, int x,
   int y, int w, int h)
{
    Bitmap bmp = new Bitmap(w, h);
    System.Drawing.Rectangle rect =
 new System.Drawing.Rectangle(0, 0, bmp.Width, bmp.Height);
    BitmapData bmpData =
    bmp.LockBits(rect, ImageLockMode.ReadWrite, PixelFormat.Format32bppRgb);
    IntPtr ptr = bmpData.Scan0;
    System.Runtime.InteropServices.Marshal.Copy(rgbData, 0, ptr, rgbData.Length);
    bmp.UnlockBits(bmpData);
    graphics.DrawImage(bmp, x, y);
}

下面简单介绍一下图形库,功能基本和桌面平台类似。

颜色

/**
 * The solid (full opaque) red color in the ARGB space
 */
Color redColor = new Color(0xffff0000, false);

/**
 * The semi-opaque green color in the ARGB space (alpha is 0x78)
 */
Color greenColor = new Color(0x7800ff00, true);

/**
 * The semi-opaque blue color in the ARGB space (alpha is 0x78)
 */
Color blueColor = new Color(0x780000ff, true);
/**
 * The semi-opaque yellow color in the ARGB space ( alpha is 0x78)
 */
Color yellowColor = new Color(0x78ffff00, true);

/**
 * The dash array
 */
int[] dashArray = { 20, 8 };
graphics2D.Reset();
graphics2D.Clear(Color.Black);
SolidBrush brush = new SolidBrush(redColor);
graphics2D.FillOval(brush, 30, 60, 80, 80);
brush = new SolidBrush(greenColor);
graphics2D.FillOval(brush, 60, 30, 80, 80);
Pen pen = new Pen(yellowColor, 10, Pen.CapButt, Pen.JoinMiter, dashArray, 0);
brush = new SolidBrush(blueColor);
graphics2D.SetPenAndBrush(pen, brush);
graphics2D.FillOval(null, 90, 60, 80, 80);
graphics2D.DrawOval(null, 90, 60, 80, 80);
Invalidate();

线段接头(LineCap)

Color blackColor = new Color(0x000000);
Color whiteColor = new Color(0xffffff);
graphics2D.Reset();
graphics2D.Clear(Color.White);

Pen pen = new Pen(blackColor, 20, Pen.CapButt, Pen.JoinMiter);
graphics2D.DrawLine(pen, 40, 60, 140, 60);
pen = new Pen(whiteColor, 1);
graphics2D.DrawLine(pen, 40, 60, 140, 60);

pen = new Pen(blackColor, 20, Pen.CapRound, Pen.JoinMiter);
graphics2D.DrawLine(pen, 40, 100, 140, 100);
pen = new Pen(whiteColor, 1);
graphics2D.DrawLine(pen, 40, 100, 140, 100);

pen = new Pen(blackColor, 20, Pen.CapSquare, Pen.JoinMiter);
graphics2D.DrawLine(pen, 40, 140, 140, 140);
pen = new Pen(whiteColor, 1);
graphics2D.DrawLine(pen, 40, 140, 140, 140);
Invalidate();

梨子

最后一个例子是利用各种几何图形通过“加”,“减”,“并”操作组成一个梨子图形。

Ellipse circle, oval, leaf, stem;
Area circ, ov, leaf1, leaf2, st1, st2;
circle = new Ellipse();
oval = new Ellipse();
leaf = new Ellipse();
stem = new Ellipse();
circ = new Area(circle);
ov = new Area(oval);
leaf1 = new Area(leaf);
leaf2 = new Area(leaf);
st1 = new Area(stem);
st2 = new Area(stem);
graphics2D.Reset();
graphics2D.Clear(Color.White);
int w = screenWidth;
int h = screenHeight;
int ew = w / 2;
int eh = h / 2;
SolidBrush brush = new SolidBrush(Color.Green);
graphics2D.DefaultBrush = brush;
// Creates the first leaf by filling the intersection of two Area
//objects created from an ellipse.
leaf.SetFrame(ew - 16, eh - 29, 15, 15);
leaf1 = new Area(leaf);
leaf.SetFrame(ew - 14, eh - 47, 30, 30);
leaf2 = new Area(leaf);
leaf1.Intersect(leaf2);
graphics2D.Fill(null, leaf1);

// Creates the second leaf.
leaf.SetFrame(ew + 1, eh - 29, 15, 15);
leaf1 = new Area(leaf);
leaf2.Intersect(leaf1);
graphics2D.Fill(null, leaf2);

brush = new SolidBrush(Color.Black);
graphics2D.DefaultBrush = brush;

// Creates the stem by filling the Area resulting from the
//subtraction of two Area objects created from an ellipse.
stem.SetFrame(ew, eh - 42, 40, 40);
st1 = new Area(stem);
stem.SetFrame(ew + 3, eh - 47, 50, 50);
st2 = new Area(stem);
st1.Subtract(st2);
graphics2D.Fill(null, st1);

brush = new SolidBrush(Color.Yellow);
graphics2D.DefaultBrush = brush;

// Creates the pear itself by filling the Area resulting from the
//union of two Area objects created by two different ellipses.
circle.SetFrame(ew - 25, eh, 50, 50);
oval.SetFrame(ew - 19, eh - 20, 40, 70);
circ = new Area(circle);
ov = new Area(oval);
circ.Add(ov);
graphics2D.Fill(null, circ);
Invalidate();

 

Windows Mobile引路蜂地图开发示例:概述

引路蜂地图开发包以.Net Framework类库形式支持Windows Mobile 6.x ,Windows Phone 7 , iPhone (MonoTouch) 以及Windows ,Mono (Linux GTK 等)。首先先介绍在Windows Mobile 6.x平台上开发的示例。

引路蜂地图开发包在Windows Mobile平台中除了提供地图开发API外,还包含了一个二维图形库,可以很好的弥补System.Drawing2D 在Compact Framework上的不足,它提供了:支持各种基本图形:曲线,矩形,椭圆等;支持绘制任意几何图形;支持在图形,文体,图象上的碰撞检测;增强的颜色扶持及颜色管理;控制图形绘制的质量;反走样;透明度,填充,矢量字体,几何变换以及图象,图标绘制等。

地图开发包提供与Java ME, Android平台类似的接口,支持地图放大缩小,地图平移 ,坐标转换 ,地址查询 ,经纬度反向地址查询 ,路径查询等。

 

Android引路蜂地图开发示例:离线地图示例

在手机上,离线地图包一般放在SD卡上,然后通过文件读取。引路蜂地图开发包支持同时读取多个地图包,这是通过MapTiledZone类和MapTileStreamReader类来完成的。每个地图包对应于一个MapTiledZone,地图包对每张地图建立的索引以加速图片检索将记录地图包存放的区域和缩放级别。而类MapTileStreamReader提供了管理这些地图包的方法.

离线地图工具参见: 和

下面的示例从SD卡guidebee子目录读取world.map, china.map, nanjing.map ,更一般的做法是读取子目录下所有扩展名为.map 离线地图包,这样可以随时添加或删除离线地图包。因为是示例,所以在代码中指定读取这三个地图包。下面离线地图包为例子中用到的,是使用离线工具制作的。

文件名 大小 说明 下载
world.map 449k 世界地图 1-4级 下载
china.map 181M 中国地图 4-11级 下载
nanjing.map 64M 南京地图 11-17级 下载

随着缩放级别的增大,需下载的离线地图图片呈现指数增长,一般不可能将一个国家从1到17放在一个离线地图包,可以分几个层次下载制作离线地图包。如上图分3个层次。世界–>中国–>南京。

例子从SD卡中读取地图文件,在项目中添加一个MapTiledZone的派生类FileMapTiledZone ,从文件中读取离线地图。

package com.pstreets.gisengine.demo;
import com.mapdigit.gis.raster.MapTiledZone;
import com.mapdigit.util.Log;
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

public class FileMapTiledZone extends MapTiledZone{

    ////////////////////////////////////////////////////////////////////////////
    //--------------------------------- REVISIONS ------------------------------
    // Date       Name                 Tracking #         Description
    // ---------  -------------------  -------------      ----------------------
    // 14AUG2009  James Shen                            Code review
    ////////////////////////////////////////////////////////////////////////////
    /**
     * constructor.
     * @param fileName  file name of the map zone.
     * @param isMarkSupported  does device support mark or not.
     * @throws FileNotFoundException
     */
    public FileMapTiledZone(String fileName,boolean isMarkSupported)
     throws FileNotFoundException  {
        super(null);
        this.fileName=fileName;
        this.isMarkSupported=isMarkSupported;
       

    }

    ////////////////////////////////////////////////////////////////////////////
    //--------------------------------- REVISIONS ------------------------------
    // Date       Name                 Tracking #         Description
    // ---------  -------------------  -------------      ----------------------
    // 14AUG2009  James Shen                            Code review
    ////////////////////////////////////////////////////////////////////////////
    /**
     * constructor.
     * @param fileName  file name of the map zone.
     * @throws FileNotFoundException
     */
    public FileMapTiledZone(String fileName) throws FileNotFoundException  {
        this(fileName,false);
    }

    public void ensureClose() throws IOException {
        if (!isMarkSupported) {
            super.ensureClose();
            try {
             dataInputStream.close();
                fileConnection.close();
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
    }

     public DataInputStream skipBytes(long offset) {
        try {
         fileConnection=new FileInputStream(fileName);
            dataInputStream=new DataInputStream(fileConnection);
            if (offset > 0) {
             fileConnection.skip(offset);
            }

        } catch (Exception e) {
            Log.p("seek error:" + e.getMessage(), Log.ERROR);
        }
        return dataInputStream;
    }

    private FileInputStream fileConnection=null;
    private DataInputStream dataInputStream;
}

下面代码从SD卡上读取离线地图信息:

package com.pstreets.gisengine.demo;

import com.mapdigit.gis.geometry.GeoLatLng;
import com.mapdigit.gis.raster.MapTileStreamReader;
import com.mapdigit.gis.raster.MapType;
import com.pstreets.gisengine.R;
import com.pstreets.gisengine.SharedMapInstance;

import android.app.Activity;
import android.os.Bundle;

public class StoredMap extends Activity {

 public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.main);
 }

 public void onStart() {
  super.onStart();
  GeoLatLng center = new GeoLatLng(32.0616667, 118.7777778);
  SharedMapInstance.map.setCenter(center, 4, MapType.MICROSOFTCHINA);
  MapTileStreamReader streamReader=SharedMapInstance
     .mapTileDownloadManager.getInteralMapTileStreamReader();

  try{
   FileMapTiledZone mapTileZone
          =new FileMapTiledZone("/sdcard/guidebee/world.map",false);
   streamReader.addZone(mapTileZone);
   mapTileZone=new FileMapTiledZone("/sdcard/guidebee/china.map",false);
   streamReader.addZone(mapTileZone);
   mapTileZone=new FileMapTiledZone("/sdcard/guidebee/nanjing.map",false);
   streamReader.addZone(mapTileZone);
   streamReader.open();
  }catch(Exception e){
   
  }

 }

}

MapTileManager 内部定义一个MapTileStreamReader ,如果设置了本地地图,MapTileManager会先从本地读取,如果本地没有需要图片,然后再从地图服务器上下载。可以使用MapTileStreamReader的addZone 方法添加多个地图包,地图包可以有重叠的区域,这时添加的先后顺序对结果就会影响,如果有多个,则取第一个。

 

Android引路蜂地图开发示例:叠加自定义图层

在开发应用的过程中,常常需要在地图上需绘制一点自定义的兴趣点或是自定义的几何图形。引路蜂地图包中RasterMap最终提供的基实就是一张图片。

protected void paint(Graphics g){
    map.paint(mapGraphics);
	g.drawImage((Image) mapImage.getNativeImage(), 0, 0, 0);        
	//start drawing your own sharps or images.
	... …
	}

所以一个简单的方法是绘制完地图后,你可以使用任何绘图方法在地图绘制任何自定义的图形或是图象。
这里需要注意的是坐标变换,RasterMap采用的是经纬度坐标,而屏幕显示采用的屏幕坐标,RasterMap提供了坐标转换的方法:fromScreenPixelToLatLng 坐屏幕坐标转换成地图经纬度坐标。fromLatLngToScreenPixel 从经纬度坐标转换成屏幕坐标。
下面给出的例子是采用派生MapLayer子类的方法,RasterMap是 MapLayerContainer的子类,可以用来管理多个地图层。这些图层从下到上相当于透明纸一层一层叠加形成最终显示的地图。
例子中显示几个自定义兴趣点,和一个三角形,并中地图中心显示十字标。

package com.pstreets.gisengine.demo;

import com.mapdigit.gis.MapLayer;
import com.mapdigit.gis.drawing.IGraphics;
import com.mapdigit.gis.geometry.GeoLatLng;
import com.mapdigit.gis.geometry.GeoPoint;
import com.mapdigit.gis.raster.MapType;
import com.pstreets.gisengine.GuidebeeMapView;
import com.pstreets.gisengine.R;
import com.pstreets.gisengine.SharedMapInstance;

import android.app.Activity;
import android.os.Bundle;

public class MapOverlay extends Activity {

  OverLayMapLayer mapLayer;
 public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.main);
 }

 public void onStart() {
  super.onStart();
  GeoLatLng center = new GeoLatLng(32.0616667, 118.7777778);
  SharedMapInstance.map.setCenter(center, 9, MapType.MICROSOFTCHINA);
  mapLayer = new OverLayMapLayer(SharedMapInstance.map.getScreenWidth(),
    SharedMapInstance.map.getScreenHeight());
  SharedMapInstance.map.addMapLayer(mapLayer);

 }
 
 class OverLayMapLayer extends MapLayer {

        GeoLatLng pt1 = new GeoLatLng(32.345281, 118.84261);
        GeoLatLng pt2 = new GeoLatLng(32.05899, 118.62789);
        GeoLatLng pt3 = new GeoLatLng(32.011811, 118.798656);

        public OverLayMapLayer(int width, int height) {
            super(width, height);
        }

        public void paint(IGraphics graphics, int offsetX, int offsetY) {
            drawCursor(graphics);
            drawTriangle(graphics);
            drawPoint(graphics, pt1);
            drawPoint(graphics, pt2);
            drawPoint(graphics, pt3);

        }

        public void drawTriangle(IGraphics g) {
            GeoPoint ptOnScreen1 = SharedMapInstance.map.fromLatLngToScreenPixel(pt1);
            GeoPoint ptOnScreen2 = SharedMapInstance.map.fromLatLngToScreenPixel(pt2);
            GeoPoint ptOnScreen3 = SharedMapInstance.map.fromLatLngToScreenPixel(pt3);
            g.setColor(0xFF0000FF);
           
            g.drawLine((int) ptOnScreen1.x, (int) ptOnScreen1.y,
                    (int) ptOnScreen2.x, (int) ptOnScreen2.y);
            g.drawLine((int) ptOnScreen2.x, (int) ptOnScreen2.y,
                    (int) ptOnScreen3.x, (int) ptOnScreen3.y);
            g.drawLine((int) ptOnScreen1.x, (int) ptOnScreen1.y,
                    (int) ptOnScreen3.x, (int) ptOnScreen3.y);
        }

        public void drawPoint(IGraphics g, GeoLatLng pt) {
            GeoPoint ptOnScreen = SharedMapInstance.map.fromLatLngToScreenPixel(pt);
            int x = (int) ptOnScreen.x;
            int y = (int) ptOnScreen.y;
            g.setColor(0xFF00FF00);
            g.fillRect(x - 4, y - 4, 8, 8);

        }

        private void drawCursor(IGraphics g) {
           
            int mapWidth=getScreenWidth();
      int mapHeight=getScreenHeight();
      int x = mapWidth;
      int y = mapHeight;
            g.setColor(0xFF205020);
            g.drawRect(x - 4, y - 4, 8, 8);
            g.drawLine(x, y - 6, x, y - 2);
            g.drawLine(x, y + 6, x, y + 2);
            g.drawLine(x - 6, y, x - 2, y);
            g.drawLine(x + 6, y, x + 2, y);
        }
    }

}

 

Android自定义地图示例:QQ地图

再来一个简单一些的自定义地图类型示例:QQ地图,QQ地图URL规则比较简单。1-17级都是有一层组成,选用GENERIC_MAPTYPE_5作为QQ地图类型,直接在CustomMap修改代码如下:

package com.pstreets.gisengine.demo;
import com.mapdigit.gis.raster.ICustomMapType;
import com.mapdigit.gis.raster.MapType;
import com.mapdigit.gis.geometry.GeoLatLng;
import com.pstreets.gisengine.R;
import com.pstreets.gisengine.SharedMapInstance;
import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;

public class CustomMap extends Activity {

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
	}

	@Override
	public void onStart() {
		super.onStart();
		MapType.setCustomMapTileUrl(new TiandiMapType());
		GeoLatLng center = new GeoLatLng(32.0616667, 118.7777778);
		SharedMapInstance.map.setCenter(center, 13, MapType.GENERIC_MAPTYPE_5);

	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		MenuInflater inflater = getMenuInflater();
		inflater.inflate(R.menu.mapzoom_menu, menu);
		return true;
	}

	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
		// Handle item selection
		switch (item.getItemId()) {
		case R.id.zoomin:
			SharedMapInstance.map.zoomIn();

			return true;
		case R.id.zoomout:
			SharedMapInstance.map.zoomOut();
			return true;

		default:
			return super.onOptionsItemSelected(item);
		}

	}

}

class TiandiMapType implements ICustomMapType {

    private static int serverIndex=1;

    public String getTileURL(int mtype, int x, int y, int zoomLevel) {
        String returnURL="";
        serverIndex+=1;
        serverIndex%=3;
        int maxTiles=(int)Math.pow(2, zoomLevel);
        switch(mtype){
            case MapType.GENERIC_MAPTYPE_5:
            	 returnURL= "http://p" 
            		 + serverIndex+".map.qq.com/maptiles/" ;
                 y=maxTiles-y-1;
                 returnURL+=+zoomLevel
				   +"/"+(int)(x/16)+"/"+(int)(y/16)+"/"+x+"_"+y+".gif";
                break;
            case MapType.GENERIC_MAPTYPE_6:

                if(zoomLevel<11){
                   returnURL= "http://tile" 
                	   + serverIndex+".tianditu.com/DataServer?T=A0512_EMap";
                   returnURL+="&X="+x+"&Y="+y+"&L="+zoomLevel;

                }else if(zoomLevel<13){
                   returnURL= "http://tile" 
                	   + serverIndex+".tianditu.com/DataServer?T=B0627_EMap1112";
                   returnURL+="&X="+x+"&Y="+y+"&L="+zoomLevel;
                }else{
                   returnURL= "http://tile" 
                	   + serverIndex+".tianditu.com/DataServer?T=siwei0608";
                   returnURL+="&X="+x+"&Y="+y+"&L="+zoomLevel;
                }
                 
                break;
            case MapType.GENERIC_MAPTYPE_7:
                if(zoomLevel<11){
                   returnURL= "http://tile" 
                	   + serverIndex+".tianditu.com/DataServer?T=AB0512_Anno";
                   returnURL+="&X="+x+"&Y="+y+"&L="+zoomLevel;
                }else{
                   returnURL=MapType.EMPTY_TILE_URL;
                }
                 
                break;

        }
        return returnURL;
    }

}
 

QQ地图的分片方法和Google中国地图分片方法一致,经纬度坐标无需调整。