天气获取通过经纬度或城市信息

天气获取通过经纬度或城市信息
前言:
由于项目需要为手机等其他内网终端提供天气信息,因为终端和手机都访问的是内网,所以天气信息我们需要自己获取并转存到我们自己自己的数据库,然后再对终端提供查询服务。
实现准备:
在网上百度了下,好多天气提供都是需要收费的,个人觉得中国天气网的信息够用免费,而且也很方便我们取数据存储。先来看看中国天气网的对外接口, http://flash.weather.com.cn/wmaps/xml/china.xml ,这个很好的一点就是xml结构,解析简单,而且如果你要获取湖北省的天气,只需要把china.xml改为hubei.xml,如果要获取市内各区天气,那你懂的,而且上级xml的各城市节点里面也包含城市拼音的哦,是不是一下思路明了了?

实现思路:
关键点
1、自动任务定时执行获取天气
2、经纬度转为城市名(这里本来是可以用谷歌的api转换的,但是在国内没有办法,所以选择了百度,据说腾讯也有)
3、用反射解析转为天气对象保存
这里设计的思路是城市的经纬度、名称一般是多少年都不会变化的,所以全中国的城市可以一次经纬度转换,长久使用。获取天气呢采用迭代逐级获取,即获取一级天气后通过城市节点解析取城市名,然后组织url继续获取二级,直到取到区。这个层级可以手动设置,就看大家接口怎么写了,另外这个迭代,如果服务器条件允许那就可以开多线程处理。
转存的表设计:
经纬度转换存储表
CREATE TABLE `jc_longitude` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
`lng_min` varchar(255) DEFAULT NULL COMMENT '最小经度',
`lng_max` varchar(255) DEFAULT NULL COMMENT '最大经度',
`lat_min` varchar(255) DEFAULT NULL COMMENT '最小维度',
`lat_max` varchar(255) DEFAULT NULL COMMENT '最大维度',
`api_lng` varchar(255) DEFAULT NULL COMMENT 'API获取的经度',
`api_lat` varchar(255) DEFAULT NULL COMMENT 'API获取的维度',
`city_name` varchar(255) DEFAULT NULL COMMENT '城市名称',
`city_code` varchar(255) DEFAULT NULL COMMENT '城市码',
`city_py` varchar(255) DEFAULT NULL COMMENT '城市名拼音',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
天气转存表:
CREATE TABLE `jc_weather` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
`qu_name` varchar(255) DEFAULT NULL COMMENT '区域名称',
`py_name` varchar(255) DEFAULT NULL COMMENT '拼音名称',
`cityname` varchar(255) DEFAULT NULL COMMENT '城市名称',
`state1` varchar(255) DEFAULT NULL COMMENT '前天气状态',
`state2` varchar(255) DEFAULT NULL COMMENT '后天气状态',
`state_detailed` varchar(255) DEFAULT NULL COMMENT '天气状态描述',
`tem1` varchar(255) DEFAULT NULL COMMENT '最高气温',
`tem2` varchar(255) DEFAULT NULL COMMENT '最低气温',
`windState` varchar(255) DEFAULT NULL COMMENT '风向状态描述',
`city_x` varchar(255) DEFAULT NULL COMMENT '城市坐标经度',
`city_y` varchar(255) DEFAULT NULL COMMENT '城市坐标维度',
`centername` varchar(255) DEFAULT NULL COMMENT '中心名称',
`tem_now` varchar(255) DEFAULT NULL COMMENT '当前温度',
`wind_dir` varchar(255) DEFAULT NULL COMMENT '风向',
`wind_power` varchar(255) DEFAULT NULL COMMENT '风力',
`humidity` varchar(255) DEFAULT NULL COMMENT '湿度',
`time` varchar(255) DEFAULT NULL COMMENT '气象发布时间',
`url` varchar(255) DEFAULT NULL COMMENT '城市编码',
`weather_date` timestamp NULL DEFAULT NULL COMMENT '天气日期',
`weather_type` varchar(255) DEFAULT NULL COMMENT '天气信息类别,1:省级天气,2:市级天气,3:区级天气',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
上核心代码:
自动任务的我就不展示给大家看了,就从我写的jUnit单元测试开始吧。关键地方看注释。
ApplicationContext ac = getContext();
SyncWeatherService weaService = (SyncWeatherService) ac.getBean(SyncWeatherService.class.getSimpleName());
String syncUrl = "http://flash.weather.com.cn/wmaps/xml/china.xml";
//String syncUrl = "http://flash.weather.com.cn/wmaps/xml/xisha.xml";
if(StringUtils.isNotBlank(syncUrl)){
logger.info("同步获取天气信息url:"+syncUrl);
//httpClient发请求
String reStr = HttpClientUtil.getInstance().get(syncUrl);
boolean isOk = weaService.syncWeather(reStr,syncUrl);
//解析xml
Date now = new Date();
if(StringUtils.isNotBlank(reStr)){
if("success".equals(reStr)){
logger.info("同步获取天气信息自动任务"+DateFormatUtils.formatDateTime(now)+"执行结果:"+reStr);
}else{
logger.error("同步获取天气信息自动任务"+DateFormatUtils.formatDateTime(now)+"执行结果:"+reStr);
}
}
}

weaService:
3个方法,syncWeather从外网获取天气,setLevelWeather获取下级天气情况,getCityWeaList获取天气保存数据库。
@Override
public boolean syncWeather(String reStr, String syncUrl) {
boolean isOk = true;
if(StringUtils.isNotBlank(reStr)){
try {
//先删除所有信息
weatherMng.deleteAll();
Integer cityType = 1;
List<Map<String, Object>> mpList = XmlUtil.xmlStrToListMap(reStr);
if(reStr.startsWith("<china")){
cityType = 1;
}
if(!CollectionUtils.isEmpty(mpList)){
List<CmsWeather> weaList = new ArrayList<>();
Field[] fields = ReflectUtil.getField(CmsWeather.class);
if(fields != null && fields.length > 0){
weaList = getCityWeaList(fields,mpList,cityType);
}
if(!CollectionUtils.isEmpty(weaList)){
cityType = 2; //省级--->市县
weaList = setLevelWeather(weaList,cityType,syncUrl,fields);
logger.info("省级--->市县---天气同步完毕");
//市县级-->区级
cityType = 3; //市县级-->区级
setLevelWeather(weaList,cityType,syncUrl,fields);
logger.info("市县级-->区级---天气同步完毕");
}
//地理信息
//longitudeMng
CmsWeather tmpWea = new CmsWeather();
List<CmsWeather> weaDbLs = weatherMng.queryWeatherBy(tmpWea);
if(!CollectionUtils.isEmpty(weaDbLs)){
//这里通过谷歌的一个url请求获取城市的地理信息,可能会被墙,不过还好地理位置很长时间试只取一次(事实证明确实被墙,那这张表的数据可能需要现场导入,百度也可以做到,可是百度要appkey,切返回的没有范围区间,mdbd)
//String longitudeUrl = "http://maps.google.com/maps/api/geocode/json?address=";
for(int i = 0;i < weaDbLs.size();i++){
CmsWeather wea = weaDbLs.get(i);
String cityName = wea.getCityname();
String weaType = wea.getWeatherType();
if(StringUtils.isNotBlank(weaType) && ("2".equals(weaType) || "3".equals(weaType))){
CmsLongitude tmpLgt = new CmsLongitude();
tmpLgt.setCityName(cityName);
List<CmsLongitude> logitudeLs = longitudeMng.queryBy(tmpLgt);
if(CollectionUtils.isEmpty(logitudeLs)){
GeoPoint gp = LocationUtil.getFromLocationName(cityName,"baidu");
if(gp != null){
CmsLongitude saveLgt = new CmsLongitude();
saveLgt.setCityCode(wea.getUrl());
saveLgt.setCityName(wea.getCityname());
saveLgt.setCityPy(wea.getPyName());
saveLgt.setLatMax(String.valueOf(gp.getLatMax()));
saveLgt.setLatMin(String.valueOf(gp.getLatMin()));
saveLgt.setLngMax(String.valueOf(gp.getLngMax()));
saveLgt.setLngMin(String.valueOf(gp.getLngMin()));
saveLgt.setApiLat(String.valueOf(gp.getApiLat()));
saveLgt.setApiLng(String.valueOf(gp.getApiLng()));
saveLgt = longitudeMng.save(saveLgt);
}
}
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
isOk = false;
}
}
return isOk;
}

private List<CmsWeather> setLevelWeather(List<CmsWeather> weaList, Integer cityType,String syncUrl, Field[] fields) throws Exception {
List<CmsWeather> nextLevelWeaLs = new ArrayList<>();
CmsWeather tmpWea = null;
String tmpPyName = "";
if(StringUtils.isNotBlank(syncUrl)){
Integer subIndex = syncUrl.lastIndexOf("/");
String baseUrl = syncUrl.substring(0, subIndex) + "/";
List<Map<String, Object>> weaMpLs = new ArrayList<>();
List<CmsWeather> tmpNextList = new ArrayList<>();
for(int i = 0;i < weaList.size();i++){
tmpWea = weaList.get(i);
tmpPyName = tmpWea.getPyName();
if(StringUtils.isNotBlank(tmpPyName)){
String provincialUrl = baseUrl + tmpPyName + ".xml";
logger.debug("-----------------");
logger.debug("-------------provincialUrl:"+provincialUrl);
logger.debug("===================");
String weaStr = HttpClientUtil.getInstance().get(provincialUrl);
if(StringUtils.isNotBlank(weaStr) && weaStr.indexOf("<!DOCTYPE HTML>") < 0){
weaMpLs = XmlUtil.xmlStrToListMap(weaStr);
if(!CollectionUtils.isEmpty(weaMpLs)){
tmpNextList = this.getCityWeaList(fields,weaMpLs,cityType);
nextLevelWeaLs.addAll(tmpNextList);
weaMpLs.clear();
tmpNextList.clear();
}
}
}
}
}
return nextLevelWeaLs;
}

private List<CmsWeather> getCityWeaList(Field[] fields, List<Map<String, Object>> mpList, Integer cityType) throws Exception {
List<CmsWeather> weaList = new ArrayList<>();
Map<String, Object> tmp = null;
CmsWeather pTmpWea = null;
for(int j = 0;j < mpList.size();j++){
tmp = mpList.get(j);
pTmpWea = new CmsWeather();
for(int m = 0 ;m < fields.length;m++){
Field fld = fields[m];
String fldName = ReflectUtil.getFieldName(fld);
if(StringUtils.isNotBlank(fldName)){
Object fldVal = tmp.get(fldName);
if(fldVal != null){
pTmpWea = (CmsWeather) ReflectUtil.setEntityValue(fldName, fldVal,pTmpWea);
pTmpWea.setWeatherType(cityType.toString());
}
}
}
pTmpWea = weatherMng.save(pTmpWea);
weaList.add(pTmpWea);
}
return weaList;
}
反射工具类:(自己写的,很简单,大家可以完善)
public class ReflectUtil {
public static Field[] getField(Class<?> model){
Field[] field = model.getDeclaredFields(); // 获取实体类的所有属性,返回Field数组
return field;
}
public static String getFieldName(Field field){
return field.getName();
}
public static String getFieldType(Field field){
return field.getGenericType().toString();
}
public static Object getFieldValue(Field field,Object entity){
String name = getFieldName(field);
Object value = null;
String type = field.getGenericType().toString(); // 获取属性的类型
try {
if (type.equals("class java.lang.String")) { // 如果type是类类型,则前面包含"class ",后面跟类名
Method m = entity.getClass().getMethod("get" + upperCaseFirst(name));
value = (String) m.invoke(entity); // 调用getter方法获取属性值
/*反射设置方法
* if (value == null) {
m = entity.getClass().getMethod("set"+name,String.class);
m.invoke(entity, "");
}*/
}
if (type.equals("class java.lang.Integer")) {
Method m = entity.getClass().getMethod("get" + upperCaseFirst(name));
value = (Integer) m.invoke(entity);
}
if (type.equals("class java.lang.Boolean")) {
Method m = entity.getClass().getMethod("get" + upperCaseFirst(name));
value = (Boolean) m.invoke(entity);
}
if (type.equals("class java.util.Date")) {
Method m = entity.getClass().getMethod("get" + upperCaseFirst(name));
value = (Date) m.invoke(entity);
}
} catch (Exception e) {
e.printStackTrace();
}
return value;
}
public static String upperCaseFirst (String str){
StringBuilder sb = new StringBuilder(str);
sb.setCharAt(0, Character.toUpperCase(sb.charAt(0)));
return str = sb.toString();
}
public static Object setEntityValue(String attrName, Object attrValue,Object entity) throws Exception {
//反射设置方法
if(StringUtils.isNotBlank(attrName) && attrValue != null) {
String type = attrValue.getClass().toString();
Method m = null;
switch (type) {
case "class java.lang.String":
m = entity.getClass().getMethod("set"+upperCaseFirst(attrName),String.class);
break;
case "class java.lang.Integer":
m = entity.getClass().getMethod("set"+upperCaseFirst(attrName),Integer.class);
break;
case "class java.lang.Boolean":
m = entity.getClass().getMethod("set"+upperCaseFirst(attrName),Boolean.class);
break;
case "class java.util.Date":
m = entity.getClass().getMethod("set"+upperCaseFirst(attrName),Date.class);
break;
}
if(m != null){
m.invoke(entity, attrValue);
}
}
return entity;
}
}
GeoPoint 地理位置实体
public class GeoPoint {
/*int lat;
int lng;
public GeoPoint(int lat, int lng) {
this.lat = lat;
this.lng = lng;
}*/
private double lngMin;
private double lngMax;
private double latMin;
private double latMax;
private double apiLng;
private double apiLat;
public GeoPoint(double lngMin, double lngMax, double latMin, double latMax,double apiLng,double apiLat) {
this.lngMin = lngMin;
this.lngMax = lngMax;
this.latMin = latMin;
this.latMax = latMax;
this.apiLng = apiLng;
this.apiLat = apiLat;
}
public double getLngMin() {
return lngMin;
}
public void setLngMin(double lngMin) {
this.lngMin = lngMin;
}
public double getLngMax() {
return lngMax;
}
public void setLngMax(double lngMax) {
this.lngMax = lngMax;
}
public double getLatMin() {
return latMin;
}
public void setLatMin(double latMin) {
this.latMin = latMin;
}
public double getLatMax() {
return latMax;
}
public void setLatMax(double latMax) {
this.latMax = latMax;
}
public double getApiLng() {
return apiLng;
}
public void setApiLng(double apiLng) {
this.apiLng = apiLng;
}
public double getApiLat() {
return apiLat;
}
public void setApiLat(double apiLat) {
this.apiLat = apiLat;
}
}
LocationUtil 地理位置工具类,这个类很重要,这个里面就是把城市名传给API接口,返回经纬度,然后我自己再将经纬度存储,里面要说到的是关于最大最小经纬度,这里目前是+-0.5,这个精度应该来说还是稍微有点大,大家可以自己调整,大家这里可以想想为什么我这里取到城市的经纬度我要自己再转换一个最大最小值存储?
public class LocationUtil {
public static GeoPoint getFromLocationName(String address,String supper) {
JSONObject jsonObject = new JSONObject();
if(StringUtils.isNotBlank(supper)){
switch(supper){
case "google":
jsonObject = googelResult(address);
break;
case "baidu":
jsonObject = baiduResult(address);
break;
}
}
return getGeoPoint(jsonObject,supper);
}
private static JSONObject baiduResult(String address) {
//这里的ak秘钥码是要大家自己去百度申请的哦,地址: http://lbsyun.baidu.com/ ,所以我就不好意思了,要隐藏了。
String url = "http://api.map.baidu.com/geocoder/v2/?output=json&ak=你的秘钥码&address=";
HttpGet httpGet = new HttpGet(url + address);
HttpClient client = new DefaultHttpClient();
HttpResponse response;
StringBuilder stringBuilder = new StringBuilder();
try {
response = client.execute(httpGet);
HttpEntity entity = response.getEntity();
InputStream stream = entity.getContent();
int b;
while ((b = stream.read()) != -1) {
stringBuilder.append((char) b);
}
} catch (Exception e) {
e.printStackTrace();
}
JSONObject jsonObject = new JSONObject();
try {
jsonObject = new JSONObject(stringBuilder.toString());
} catch (JSONException e) {
e.printStackTrace();
}
return jsonObject;
}

private static JSONObject googelResult(String address){
String url = "http://maps.google.com/maps/api/geocode/json";
HttpGet httpGet = new HttpGet(url + "?sensor=false&address=" + address);
HttpClient client = new DefaultHttpClient();
//其实可以设置代理,但是那也是个坑
HttpResponse response;
StringBuilder stringBuilder = new StringBuilder();
try {
response = client.execute(httpGet);
HttpEntity entity = response.getEntity();
InputStream stream = entity.getContent();
int b;
while ((b = stream.read()) != -1) {
stringBuilder.append((char) b);
}
} catch (Exception e) {
e.printStackTrace();
}
JSONObject jsonObject = new JSONObject();
try {
jsonObject = new JSONObject(stringBuilder.toString());
} catch (JSONException e) {
e.printStackTrace();
}
return jsonObject;
}
private static GeoPoint getGeoPoint(JSONObject jsonObject,String supper) {
if(StringUtils.isNotBlank(supper)){
switch(supper){
case "google":
try {
JSONArray array = (JSONArray) jsonObject.get("results");
JSONObject first = array.getJSONObject(0);
JSONObject geometry = first.getJSONObject("geometry");
JSONObject location = geometry.getJSONObject("location");
double lat = location.getDouble("lat");
double lng = location.getDouble("lng");
//return new GeoPoint((int) (lat * 1E6), (int) (lng * 1E6));
//误差+—1度
double lngMin = lng - 0.5;
double lngMax = lng + 0.5;
double latMin = lat - 0.5;
double latMax = lat + 0.5;
return new GeoPoint(lngMin,lngMax, latMin,latMax,lng,lat);
} catch (JSONException e) {
e.printStackTrace();
}
break;
case "baidu":
try {
JSONObject resObj = jsonObject.getJSONObject("result");
JSONObject location = resObj.getJSONObject("location");
double lat = location.getDouble("lat");
double lng = location.getDouble("lng");
//return new GeoPoint((int) (lat * 1E6), (int) (lng * 1E6));
double lngMin = lng - 0.5;
double lngMax = lng + 0.5;
double latMin = lat - 0.5;
double latMax = lat + 0.5;
return new GeoPoint(lngMin,lngMax, latMin,latMax,lng,lat);
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
break;
}
}
return null;
}
}
到这里通过这些方法就将天气取到存储到本地了,经纬度的转存这里里面也有判断,会一并存储的。当然也可以事后自己通过取到的天气数据查询单独转换存储,我这里就是写的单元测试类获取存储的。这就是这个带有城市名和拼音的好处。
下面就来说说查询获取的实现,也就是对外提供服务的接口实现。
这里的思路:其实就是手机获取到经纬度后传给后台,后台先查我自己存储的经纬度表,因为我设有最大最小经纬度,那么可以理解,我的一个区域实际就是一个1*1(因为我是+-0.5)的正方形区域,找到经纬度最接近的,那么就定位到最近的城市了,剩下的就是去天气表查询数据了。
先看单元测试类
@Test
public void testLgtQuery(){
//经纬度本地转换城市
CmsLongitude tmpLgt = new CmsLongitude();
tmpLgt.setLatMin("30.476654");
tmpLgt.setLngMin("114.401248");
List<CmsLongitude> lgtLs = longitudeMng.queryBy(tmpLgt);
if(CollectionUtils.isEmpty(lgtLs)){
System.out.println("-------:"+lgtLs.size());
}
}
longitudeMng实现就一句,直接返回dao的调用结果,我们直接看dao的方法,我这里项目是用的Hibernate,如果大家是其他框架,明白思路里的自己实现吧。
@Override
public List<CmsLongitude> queryBy(CmsLongitude tmpLgt) {
String hql="select bean from CmsLongitude bean where 1=1 ";
Finder finder=Finder.create(hql);
String cityName = tmpLgt.getCityName();
if(StringUtils.isNotBlank(cityName)){
finder.append(" and cityName=:cityName");
finder.setParam("cityName", cityName);
}
String cityPy = tmpLgt.getCityPy();
if(StringUtils.isNotBlank(cityPy)){
finder.append(" and cityPy=:cityPy");
finder.setParam("cityPy", cityPy);
}
String lng = tmpLgt.getLngMin();
String lat = tmpLgt.getLatMin();
//这里的经纬度范围过滤只能粗略,会出现多个城市和区的地理位置
if(StringUtils.isNotBlank(lng) && StringUtils.isNotBlank(lat)){
finder.append(" and lngMin <:lng");
finder.setParam("lng", lng);
finder.append(" and lngMax >:lng");
finder.setParam("lng", lng);
finder.append(" and latMin <:lat");
finder.setParam("lat", lat);
finder.append(" and latMax >:lat");
finder.setParam("lat", lat);
}
String cityCode = tmpLgt.getCityCode();
if(StringUtils.isNotBlank(cityCode)){
finder.append(" and cityCode=:cityCode");
finder.setParam("cityCode", cityCode);
}
//这里的取到最近的思路其实很简单,那就是:最大 + 最小 - 当前,然后取绝对值,降序排列,那么第一个就是最接近的区域了,有点绕,好好理解下,写到这其实我很佩服写算法的哥们,这里这么简单的算法,我当时都想了不少时间,数学果然是根本。现在明白当初哥转换经纬度为什么要存储经纬度的最低值最小值了吧?
if(StringUtils.isNotBlank(lng) && StringUtils.isNotBlank(lat)){
double tmpLng = Double.parseDouble(lng) * 2;
double tmpLat = Double.parseDouble(lat) * 2;
finder.append(" order by ABS(bean.lngMin + bean.lngMax -:tmpLng2) asc, ABS(bean.latMin + bean.latMax -:tmpLat2) asc");
finder.setParam("tmpLng2", String.valueOf(tmpLng));
finder.setParam("tmpLat2", String.valueOf(tmpLat));
}
finder.setCacheable(true);
return find(finder);
}
上面取到的地理位置就是最接近当前经纬度的了,然后就是去查询天气表了,经纬度表里有城市名和城市码,天气表里也有的,那查询的实现不用我上代码了吧。说到这我想到这个城市码了,这个城市码我其实不知道是谁编的,也不知道是不是大家都统一用的迈步会变的,所以我还是老老实实的应的名称。
就说这么多吧,内容有点多,谢谢大家了。
有问题的可以给我留言,我有时间会解答。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

肥仔哥哥1930

来一波支持,吃不了亏上不了当

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值