上一篇我们讲到了如何选择地图服务商,这一篇接着上一篇讲下一个比较重要,且大多数开发者会碰到的问题,那就是地图纠偏
先来了解下为什么会存在地图偏移这么一个问题:先参考一个文档
http://baike.baidu.com/view/3163334.htm (百度百科对于火星坐标系统的解释)
http://www.kaixin001.com/repaste/16531379_3916220968.html?uid=16575561&urpid=3916329607
截取部分文字来看:
国家保密插件,也叫做加密插件或者加偏或者SM模组,其实就是对真实坐标系统进行人为的加偏处理,按照几行代码的算法,将真实的坐标加密成虚假的坐标,而这个加偏并不是线性的加偏,所以各地的偏移情况都会有所不同。而加密后的坐标也常被人称为火星坐标系统
从这个解释看以看出,为了保证国家的领土安全,国家在地图这一层上做了一个认为加偏处理,借助网上一个经典例子来描述:比如某广场的经纬度本身是固定的,但是国家为了保证安全,对其经纬度进行了一个加偏,比如原先是假设(20.0022211,110.2223455),经过处理之后得到的是(20.0022222,110.2223555),变化比较小,但其实已经可能已经不在某广场附近了,这样就达到了安全性,那么国内的地图服务商,GPS导航仪等则在出厂前会对检测到的经纬度同样有一个加偏,这样两个加偏,就凑到一块去了,一张完整的可以使用的地图就出来了。
相信这个例子应该能看出来为什么会存在这个地图偏移了吧,如果还有不懂可以QQ联系:565336905.
那从上面的描述中可以看出,基本国内的地图服务商其实是具有这一个加偏算法的,也就是说如果选择国内的地图服务商,其实表象是不存在地图偏移这么一个问题的,但是如果选择Google地图,我们知道Google 曾在10年申请牌照,但是没有得到批准,也就是说它是不具备地图服务提供资质的。但是依然可以使用啊,但是Google好像是10年那会关闭了纠偏接口,这样凡是国内使用Google地图的开发者来说基本都会发现取到的经纬度跟地图显示偏差很大,这就是地图偏移。
那么如何解决Google地图的偏移问题呢。
我当时做项目的过程中也是遇到这个问题,并且烦了好一阵子,但是后续通过查资料等最后获取到几个方式:
1.纠偏数据库,网上有人售卖这个地图数据库,精度有0.01及0.001的,我后续买了0.001精度的,最后的效果不错,基本看不到偏差了,杭州跟深圳两地有测试过。
2.纠偏插件,也就是一个jar包,通过这个大概1.7M的jar包,据说可以提供大致相关于0.01精度纠偏数据库的精度。不过对于移动开发者而言,或多或少会仔细考虑这1.7M 库的代价。我最后也没有购买,所以不好评价这个效果。
3.网络上有一些纠偏接口提供商,我也测试过,效果也很好,但是由于受限于自己不能控制(服务器是国外的,可能纠偏过程会很慢,又或者别人什么时候把这个服务关闭掉了,所以不敢用)。当然我会提供给大家,供测试或者不太重视这块的开发者使用吧。
最后如需购买Google地图纠偏数据库或者探讨LBS相关问题,可以联系本人:QQ:565336905,加时请注明。
以下是一个免费的地图纠偏接口,本人测试过,还不错,速度包括精度都很好,建议个人开发者学习或者测试的,可以直接使用
package *.*;//自己填写 import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URL; import java.util.List; import org.apache.http.HttpResponse; import org.apache.http.NameValuePair; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpPost; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.protocol.HTTP; import org.apache.http.util.EntityUtils; import android.os.Bundle; public class HttpUtil { /** * 发送请求 * @param url * @param method * @param params * @param enc * @return */ public static String openUrl(/blog_article/String url, String method, Bundle params, String enc/index.html){ String response = null; //GET请求 if(method.equals("GET")){ if(params != null) url = url + "?" + encodeUrl(/blog_article/params/index.html); } try { HttpURLConnection conn = (HttpURLConnection) new URL(/blog_article/url/index.html).openConnection(); conn.setRequestProperty("User-Agent", System.getProperties().getProperty("http.agent")); conn.setReadTimeout(10000); //设置超时时间 if(method.equals("POST")){ conn.setRequestMethod("POST"); conn.setDoOutput(true); if(params != null) conn.getOutputStream().write(encodeUrl(/blog_article/params/index.html).getBytes("UTF-8")); } response = read(conn.getInputStream(),enc); } catch (Exception e) { throw new RuntimeException(e.getMessage(),e); } return response; } /** * 读取输入流,转换为String * @param in * @param enc * @return * @throws IOException */ private static String read(InputStream in, String enc) throws IOException { StringBuilder sb = new StringBuilder(); BufferedReader r = null; if(enc != null){ //按指定的编码读入流 r = new BufferedReader(new InputStreamReader(in,enc), 1000); }else{ //按默认的编码读入 r = new BufferedReader(new InputStreamReader(in), 1000); } for (String line = r.readLine(); line != null; line = r.readLine()) { sb.append(line); } in.close(); return sb.toString(); } /** * 按key-Value的方式组拼请求参数 * 参数内通过“=”连接,参数间通过“&”来连接 * @param parameters * @return */ public static String encodeUrl(/blog_article/Bundle parameters/index.html) { if (parameters == null) return ""; StringBuilder sb = new StringBuilder(); //因为第一个参数和URL之间是通过"?"来连接的 boolean first = true; for (String key : parameters.keySet()) { if (first) first = false; else sb.append("&"); sb.append(key + "=" + parameters.getString(key)); } return sb.toString(); } /** * 把数据post到服务器,获取返回的结果 * * @param uriAPI * : API 所在的路径 * @param params * : 传递的数据 */ public static String postToServer(String uriAPI, List<NameValuePair> params) { /* 建立HTTPost对象 */ HttpPost httpRequest = new HttpPost(uriAPI); try { /* 添加请求参数到请求对象 */ httpRequest.setEntity(new UrlEncodedFormEntity(params, HTTP.UTF_8)); /* 发送请求并等待响应 */ HttpResponse httpResponse = new DefaultHttpClient() .execute(httpRequest); /* 若状态码为200 ok */ if (httpResponse.getStatusLine().getStatusCode() == 200) { /* 读返回数据 */ return EntityUtils.toString(httpResponse.getEntity()); } else { return "Error Response: " + httpResponse.getStatusLine().toString(); } } catch (Exception e) { return "post failure :caused by-->" + e.getMessage().toString(); } } }
package *.*;//自己填写
import android.os.Bundle; import com.google.android.maps.GeoPoint; /** * 主要解决GPS偏移的问题 * @author Administrator * */ public class GPSUtil { private static String QUERY_URL = "http://www.mapdigit.com/guidebeemap/offsetinchina.php"; private static String METHOD_GET = "GET"; /** * 获取偏移值 * @param longitude * @param latitude * @param path * @return */ public static GeoPoint adjustLoction(double longitude, double latitude) { Bundle params = new Bundle(); params.putString("lng", String.valueOf(longitude)); params.putString("lat", String.valueOf(latitude)); String offsetString = HttpUtil.openUrl(QUERY_URL, METHOD_GET, params, CustomParameter.ENCODE); int index = offsetString.indexOf(","); if (index > 0) { // 将坐标值转为18级相应的像素值 double lngPixel = lonToPixel(longitude, 18); double latPixel = latToPixel(latitude, 18); // 获取偏移值 String OffsetX = offsetString.substring(0, index).trim(); String OffsetY = offsetString.substring(index + 1).trim(); //加上偏移值 double adjustLngPixel = lngPixel + Double.valueOf(OffsetX); double adjustLatPixel = latPixel + Double.valueOf(OffsetY); //由像素值再转为经纬度 double adjustLng = pixelToLon(adjustLngPixel, 18); double adjustLat = pixelToLat(adjustLatPixel, 18); return new GeoPoint((int) (adjustLng * 1000000), (int) (adjustLat * 1000000)); } //经验公式 return new GeoPoint((int) ((latitude - 0.0025) * 1000000), (int) ((longitude + 0.0045) * 1000000)); } /** * 经度到像素X值 * * @param lng * @param zoom * @return */ public static double lonToPixel(double lng, int zoom) { return (lng + 180) * (256L << zoom) / 360; } /** * 像素X到经度 * * @param pixelX * @param zoom * @return */ public static double pixelToLon(double pixelX, int zoom) { return pixelX * 360 / (256L << zoom) - 180; } /** * 纬度到像素Y * * @param lat * @param zoom * @return */ public static double latToPixel(double lat, int zoom) { double siny = Math.sin(lat * Math.PI / 180); double y = Math.log((1 + siny) / (1 - siny)); return (128 << zoom) * (1 - y / (2 * Math.PI)); } /** * 像素Y到纬度 * * @param pixelY * @param zoom * @return */ public static double pixelToLat(double pixelY, int zoom) { double y = 2 * Math.PI * (1 - pixelY / (128 << zoom)); double z = Math.pow(Math.E, y); double siny = (z - 1) / (z + 1); return Math.asin(siny) * 180 / Math.PI; } }
由于工作需要,写了一个UITableView的子类,简单的实现了每个cell的展开和收缩的动画效果以及展开和收缩后的cell样式变化。这个效果也许你现在用不到,但是它在iOS上的效果确实很不错,也许以后你就会用到。分享给大家。给大家一个实际的效果:
ExtensibleTableView.h
// // ExtensibleTableView.h // Wow // // Created by Boris Sun on 12-6-20. // Copyright (c) 2012年 adsit. All rights reserved. // #import <UIKit/UIKit.h> @protocol ExtensibleTableViewDelegate <NSObject> @required //返回展开之后的cell - (UITableViewCell *)tableView:(UITableView *)tableView extendedCellForRowAtIndexPath:(NSIndexPath *)indexPath; //返回展开之后的cell的高度 - (CGFloat)tableView:(UITableView *)tableView extendedHeightForRowAtIndexPath:(NSIndexPath *)indexPath; @end @interface ExtensibleTableView : UITableView { //当前被展开的索引 NSIndexPath *currentIndexPath; id<ExtensibleTableViewDelegate> delegate_extend; } @property(nonatomic,retain)id delegate_extend; @property(nonatomic,retain)NSIndexPath *currentIndexPath; //将indexPath对应的row展开 - (void)extendCellAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated goToTop:(BOOL)goToTop; //将展开的cell收起 - (void)shrinkCellWithAnimated:(BOOL)animated; //查看传来的索引和当前被选中索引是否相同 - (BOOL)isEqualToSelectedIndexPath:(NSIndexPath *)indexPath; @end
ExtensibleTableView.m
// // ExtensibleTableView.m // Wow // // Created by Boris Sun on 12-6-20. // Copyright (c) 2012年 adsit. All rights reserved. // #import "ExtensibleTableView.h" @implementation ExtensibleTableView @synthesize delegate_extend; @synthesize currentIndexPath; - (id)init { currentIndexPath = nil; return [super init]; } //重写设置代理的方法,使为UITableView设置代理时,将子类的delegate_extend同样设置 - (void)setDelegate:(id<UITableViewDelegate>)delegate { self.delegate_extend = delegate; [super setDelegate:delegate]; } /* 将indexPath对应的row展开 params: animated:是否要动画效果 goToTop:展开后是否让到被展开的cell滚动到顶部 */ - (void)extendCellAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated goToTop:(BOOL)goToTop { NSLog(@"debug 2"); //被取消选中的行的索引 NSIndexPath *unselectedIndex = [NSIndexPath indexPathForRow:[currentIndexPath row] inSection:[currentIndexPath section]]; //要刷新的index的集合 NSMutableArray *array1 = [[NSMutableArray alloc]init]; //若当前index不为空 if(currentIndexPath) { //被取消选中的行的索引 [array1 addObject:unselectedIndex]; } //若当前选中的行和入参的选中行不相同,说明用户点击的不是已经展开的cell if(![self isEqualToSelectedIndexPath:indexPath]) { //被选中的行的索引 [array1 addObject:indexPath]; } //将当前被选中的索引重新赋值 currentIndexPath = indexPath; if(animated) { [self reloadRowsAtIndexPaths:array1 withRowAnimation:UITableViewRowAnimationFade]; } else { [self reloadRowsAtIndexPaths:array1 withRowAnimation:UITableViewRowAnimationNone]; } if(goToTop) { //tableview滚动到新选中的行的高度 [self scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionTop animated:YES]; } } //将展开的cell收起 - (void)shrinkCellWithAnimated:(BOOL)animated { //要刷新的index的集合 NSMutableArray *array1 = [[NSMutableArray alloc]init]; if(currentIndexPath) { //当前展开的cell的索引 [array1 addObject:currentIndexPath]; //将当前展开的cell的索引设为空 currentIndexPath = nil; [self reloadRowsAtIndexPaths:array1 withRowAnimation:UITableViewRowAnimationFade]; } } //查看传来的索引和当前被选中索引是否相同 - (BOOL)isEqualToSelectedIndexPath:(NSIndexPath *)indexPath { if(currentIndexPath) { return ([currentIndexPath row] == [indexPath row]) && ([currentIndexPath section] == [indexPath section]); } return NO; } /* 重写了这个方法,却无效,因为这个方法总在didSelect之前调用,很奇怪。因为无法重写该方法,所以ExtensibleTableView不算完善,因为还有额外的代码需要在heightForRowAtIndexPath和cellForRowAtIndexPath中。哪个找到完善的方法后希望可以与qq82934162联系或者在http://borissun.iteye.com来留言 */ //- (UITableViewCell *)cellForRowAtIndexPath:(NSIndexPath *)indexPath //{ // if([currentIndexPath row] == [indexPath row]) // { // return [self.delegate_extend tableView:self extendedCellForRowAtIndexPath:indexPath]; // } // return [super cellForRowAtIndexPath:indexPath]; //} - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { if([currentIndexPath row] == [indexPath row]) { return [self.delegate_extend tableView:self extendedHeightForRowAtIndexPath:indexPath]; } return [super rowHeight]; } @end
将这2个文件放到proj之后,要设置delegate_extend并且实现
//返回展开之后的cell
- (UITableViewCell *)tableView:(UITableView *)tableView extendedCellForRowAtIndexPath:(NSIndexPath *)indexPath;
//返回展开之后的cell的高度
- (CGFloat)tableView:(UITableView *)tableView extendedHeightForRowAtIndexPath:(NSIndexPath *)indexPath;
2个方法。
还有一点不合理的地方,我试着去解决,但是最终未果=。=!
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { //若当前行被选中,则返回展开的cell if([tableView_ isEqualToSelectedIndexPath:indexPath]) { return [self tableView:tableView extendedCellForRowAtIndexPath:indexPath]; } ... }
这里要先判断当前行是否被选中,若被选中则调用extendedCellForRowAtIndexPath方法。因为我试着重写UITableView的- (UITableViewCell *)cellForRowAtIndexPath:(NSIndexPath *)indexPath方法。试图在这个方法里做上边的事情,可是这个方法总是在didSelect方法之前被调用,因此没有达到预期的目标。
希望各位如果下载了源码,解决了这个问题的话,可以回到这里给我留言或者联系qq82934162.
以下是一个简单的demo源码:
http://dl.iteye.com/topics/download/4f242bbb-8004-3352-9604-b1211b7562df
下午弄了个jni关于java语言调用c语言的例子,虽然对于jni的相关步骤一经能熟练掌握,但是由于对于c了解的太少,因此遇到非常之多的问题,无奈只有做好详细笔记,待日后详细理解了。真是不好搞呀!!!!!!!
1、先来看本节的目标代码如下:(目的:练习java把数据传递给c代码)
package com.luochuang.fromjava; public class DataProvider { /** * 传递两个java的int数据给c语言,c语言处理完之后把数据返回 * * @param a * @param b * @return */ public native int add(int a, int b); /** * java语言直接传递JAva中的字符串给C语言,C语言处理字符串之后返回新的字符串 * * @param s * @return */ public native String sayHelloInC(String s); /** * java传递int类型的数据给c语言,c语言处理数组的每个元素,把每个元素的值加上10, 再把数据返回给java方法 * * @param Num * @return */ public native int[] intMethod(int[] Num); }
2、生成1方法中的java头文件。(来到该工程的bin/classes/目录下利用javah命令生成1类对应的头文件)
/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class com_luochuang_fromjava_DataProvider */ #ifndef _Included_com_luochuang_fromjava_DataProvider #define _Included_com_luochuang_fromjava_DataProvider #ifdef __cplusplus extern "C" { #endif /* * Class: com_luochuang_fromjava_DataProvider * Method: add * Signature: (II)I */ JNIEXPORT jint JNICALL Java_com_luochuang_fromjava_DataProvider_add (JNIEnv *, jobject, jint, jint); /* * Class: com_luochuang_fromjava_DataProvider * Method: sayHelloInC * Signature: (Ljava/lang/String;)Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_com_luochuang_fromjava_DataProvider_sayHelloInC (JNIEnv *, jobject, jstring); /* * Class: com_luochuang_fromjava_DataProvider * Method: intMethod * Signature: ([I)[I */ JNIEXPORT jintArray JNICALL Java_com_luochuang_fromjava_DataProvider_intMethod (JNIEnv *, jobject, jintArray); #ifdef __cplusplus } #endif #endif
3、把这个头文件拷贝到自己新建的jni目录下,并新建hello.c文件。
#include "com_luochuang_fromjava_DataProvider.h" #include <android/log.h> #define LOG_TAG "System.out" #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__) #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) //这里实现第一个相加的方法 JNIEXPORT jint JNICALL Java_com_luochuang_fromjava_DataProvider_add( JNIEnv * env, jobject obj, jint x, jint y) { //java中的int占四个字节,c语言也是4个字节,因此可以相互转化 LOGD("x=%d",x); LOGD("y=%d",y); int result = x + y; LOGI("result=%d",result); return x + y; }
关于这个方法的介绍:
jstring Java_cn_itcast_ndk_DemoActivity_helloFromJNI(JNIEnv *env, jobject javaThis) {
return (*env)->NewStringUTF(env, "Hello from native code!");
}
JNIEnv 类型代表了java环境 通过JNIEnv* 指针,就可以对java端的代码进行操作.
创建java类的对象,调用java对象的方法
获取java对象的属性 等等.
jobject是个什么
jobject obj 就是当前方法所在的类代表的对象.
#include <android/log.h>
#define LOG_TAG "System.out"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
这几句话的意思是在logcat中打印语句。(这里我也不太懂,书上就是这么写的,对c了解太少无奈啊)
4、声明android.mk方法(android.mk是Android操作系统编译的时候使用的编译规则文件)
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := Hello LOCAL_SRC_FILES := Hello.c #把一个函数库引入到交叉编译的系统里面 LOCAL_LDLIBS += -llog; include $(BUILD_SHARED_LIBRARY)
这里的参数的意思是:
LOCAL_PATH必须位于Android.mk文件的最开始。它是用来定位源文件的位置,$(call my-dir)的作用就是返回当前目录的路径。
include $(CLEAR_VARS)的作用是清除一些变量的值,但是LOCAL_PATH除外。
LOCAL_MODULE是用来指定当前待编译模块的名称,在示例中的模块名称为hello-jni
LOCAL_SRC_FILES是用来指定参与编译的源代码文件。这里只编译hell0-jin.c
include $(BUILD_SHARED_LIBRARY)是用来指示将当前模块编译为共享库,前缀为lib,后缀为.so。
还有另外一个BUILD_STATIC_LIBRARY,是用来指示将当前模块编译为静态库的,前缀为.a,后缀为.a。
这就是一个最简单的Android.mk的结构。可能还有另外一点需要介绍:
LOCAL_C_INCLUDES:=include 这个是用来指定在编译时即将使用的c头文件的位置,以当前目录为起点。
具体可以参考这里:android ndk 之Android.mk编写
5、来到当前文件的根目录下利用ndk-build命令编译。
6、来到当前的Activity。
package com.luochuang.fromjava; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.widget.Toast; public class HelloFromJavaActivity extends Activity { DataProvider privider; static { // 用来装载库文件,不论是JNI库文件还是非JNI库文件。在任何本地方法被调用之前必须先用这个两个方法之一把相应的JNI库文件装载。System.loadLibrary // 参数为库文件名,不包含库文件的扩展名。 System.loadLibrary("Hello"); } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); privider = new DataProvider(); } public void onClick(View view) { switch (view.getId()) { case R.id.cfromjava: int resulst = privider.add(10, 20); Toast.makeText(HelloFromJavaActivity.this, "调用结果" + resulst, 0) .show(); break; } } }
最后吐司显式结果。