精华内容
下载资源
问答
  • 一、初始全国各省份对象1.定义对象 省份名称—国际化名称变量var Langualarmap={ "安徽":"AH", "北京":"BJ", "福建":"FJ", "甘肃":"GS", "广东":"GD", "广西":"GX", "贵州":"GZ", "海南":"HI",

    一、初始全国各省份对象

    1.定义对象
    省份名称—国际化名称变量

    var Langualarmap={
            "安徽":"AH",
            "北京":"BJ",
            "福建":"FJ",
            "甘肃":"GS",
            "广东":"GD",
            "广西":"GX",
            "贵州":"GZ",
            "海南":"HI",
            "河北":"HE",
            "河南":"HA",
            "黑龙江":"HL",
            "湖北":"HB",
            "湖南":"HN",
            "吉林":"JL",
            "江苏":"JS",
            "江西":"JX",
            "辽宁":"LN",
            "内蒙古":"IM",
            "宁夏":"NX",
            "青海":"QH",
            "山东":"SD",
            "山西":"SX",
            "陕西":"SN",
            "上海":"SH",
            "四川":"SC",
            "天津":"TJ",
            "西藏":"XZ",
            "新疆":"XJ",
            "云南":"YN",
            "浙江":"ZJ",
            "重庆":"CQ",
            "澳门":"MO",
            "香港":"HK",
            "台湾":"TW",
        }

    2.获得地图对象并按照是英文还是中文初始地图省份

     allProvinceInfo = {100000: {
            //name: "全国",国际化中替换
            data: {}}}
             var allFeatures = echarts.getMap("world").geoJson.features;
             var language = showLanguage();
        for (var i = 0; i < 34; i++) {
            var name=allFeatures[i + 216].properties.name
             name=language.indexOf("en")>=0? $.i18n.prop(Langualarmap[name]):name
            allFeatures[i + 216].properties.name=name
            allProvinceInfo[allFeatures[i + 216].properties.id] = {
                index: (i + 216),
                name: name,
                offset:(
                    language.indexOf("en")>=0?
                        (
                            "810000".indexOf(allFeatures[i + 216].properties.id)>=0?
                                [114.252,22.345]
                                :"820000".indexOf(allFeatures[i + 216].properties.id)>=0? [113.565,22.17]
                                :"120000".indexOf(allFeatures[i + 216].properties.id)>=0?[116.8,39.6]
                                :allFeatures[i + 216].properties.cp
                        ):allFeatures[i + 216].properties.cp
                ),
                data: {}
            }
            allProvinceNames.push(name)
        }

    二、正式加载地图

    1.地图按照中英文来渲染
    2.单击地图点击事件
    3.触屏或双击地图点击事件

    function mapInit(rankType, data) {
        $(".left_bottom_options").fadeIn()
        var rankInfo = {
            name: "--",
            start:0,
            min: 100,
            max: 200,
            end: 300
        }
        if (rankType && rankType.name)
            rankInfo = rankType
    
        var effectScatterData=[]
        var regionDataArray=[]
        var rankData;
        for(var i in allProvinceInfo){
            if (data) {
                rankData=data[allProvinceInfo[i].name]
            }
            var regionData={
                name: allProvinceInfo[i].name,
                label:{
                    normal: {
                        show:true,
                        color:'#96D6FF',
                    }
                },
                itemStyle: {normal: {borderColor: '#005C97'}}
            }
            if(rankData)
                regionData.value=rankData
                regionDataArray.push(regionData)
        }
    
        var mapDom = $("#myMap")
        if(!echartMap){
            echartMap = echarts.init(mapDom[0])
            echartMap.on("mouseover", function (params) {//禁止外国高亮)
                if (allProvinceNames.indexOf(params.name) < 0)
                    echartMap.dispatchAction({
                        type: 'downplay',
                        name:params.name
                    })
            })
            var TimeFn = null;
            echartMap.on("click", function (params) {
                var provinceParams=getProvinceParams(params.name)
                if(provinceParams){
                    highlightProvinceParams=provinceParams
                    provinceHighlight(provinceParams.code)
                    clearTimeout(TimeFn);
                  //执行延时
                    TimeFn = setTimeout(function(){
                             //do function在此处写单击事件要执行的代码
                              myExports.showProvince(highlightProvinceParams.name)
                    },300);
    
                   // detaillDataQuery(provinceParams.code)//展开详情页
                }
                var i = function(para) {
                    if(para.name===params.name) {
                        //myExports.showProvince(params.name);
                        myExports.goToAnalysis(params.name);
                    }
                };
                echartMap.on("click", i)
                setTimeout(function(){echartMap.off("click", i)}, 300)
            })
            window.onresize = function () {//地图自适应宽高
                var receptacle=$(".echart_box")
                mapDom.width(receptacle.width())
                mapDom.height(receptacle.height())
                echartMap.resize()
                /*详情echart*/
                var graphic = $('#graphic'),listPageObj = $('#listPage');
                $('.echartDiv').width(listPageObj.width() ? listPageObj.width() : graphic.width());
                $('.echartDiv').height(graphic.height());
                if(typeof(myChart) != "undefined")
                    myChart.resize();
                //保持高亮
                keepHighlight()
            }
        }
        echartMap.setOption({
            tooltip:{
                show:true,
                trigger: 'item',
                extraCssText:'border:0;padding:0;background-color:rgba(0,0,0,0)',
                formatter:function(params){
                    var tooltipFormatter="<div class='tooltip_head'>"+params.name+$.i18n.prop('com_zte_lte_processingpoorqualitregion_header_poorqualitregion_statistics')+"</div><div class='tooltip_center'>"
    
                    if (allProvinceNames.indexOf(params.name) < 0)//禁止国外弹出
                        tooltipFormatter=null
                    else{
                        var provinceData=getRightBoxListData(params.name)
                        if(provinceData){
                            tooltipFormatter+=$.i18n.prop('com_zte_lte_processingpoorqualitregion_header_poorqualitregion_sorttype')+":"+provinceData.SORT+"<br/>";
                            tooltipFormatter+=$.i18n.prop('com_zte_lte_processingpoorqualitregion_header_poorqualitregion_dealingorder')+":"+provinceData.dealingNumber+"<br/>";
                            tooltipFormatter+=$.i18n.prop('com_zte_lte_processingpoorqualitregion_header_poorqualitregion_overtimeorder')+":"+provinceData.willNumber+"</div>";
    
                        }else{
                            tooltipFormatter+=$.i18n.prop('com_zte_lte_processingpoorqualitregion_header_poorqualitregion_sorttype')+":--<br/>";
                            tooltipFormatter+=$.i18n.prop('com_zte_lte_processingpoorqualitregion_header_poorqualitregion_dealingorder')+":--<br/>";
                            tooltipFormatter+=$.i18n.prop('com_zte_lte_processingpoorqualitregion_header_poorqualitregion_overtimeorder')+":--</div>";
    
                        }
                    }
    
                    return tooltipFormatter
                }
            },
            visualMap: [{//地图标点颜色
                type: 'piecewise',
                //splitNumber:3,//颜色区间,跟下面的pieces冲突
                pieces: [
                    {min: rankInfo.max, label: '(' + rankInfo.max +  ',+∞)'},
                    {min: rankInfo.min, max: rankInfo.max, label: '(' + rankInfo.min + ',' + rankInfo.max + ']'},
                    {max: rankInfo.min, label: '(' + rankInfo.start + ',' + rankInfo.min + ']'}
                ],
               // seriesIndex: 1,
                text: [rankInfo.name],
                showLabel: true,//设置了text后,label会默认不显示
                hoverLink: false,
                orient: 'horizontal',//横放指标
                ///dimension: 2,//数据的维度
                color: colorArry,
                textStyle: {
                    color: 'rgba(94, 202, 252, 0.8)'
                },
                left:20
            }],
            series: [
                {
                    type: 'map',
                    //geoIndex: 0,
                    mapType: 'world',
                    roam: true,//鼠标放大
                    zoom: 4.4,
                    center: [104, 37],
                    label:{
                        emphasis: {
                            show:true,
                            color:'#96D6FF',
                        },
                    },
                    itemStyle: {//默认地图颜色
                        normal: {
                            areaColor: '#041A34',
                            borderWidth: 2,
                            borderColor: '#171717'
                        }
                        ,
                        emphasis:{
                            areaColor: '#274E9D',
                            borderColor:'#96D6FF',
                            borderWidth: 3
    
                        }
                    },
                    data: regionDataArray,
                },
                {//地图定位
                    type: 'scatter',
                    coordinateSystem: 'geo',
                    data: [],
                    symbolSize: [25.7,25.7],
                    symbolOffset: [0, '-15px'],
                    silent:true,//禁止鼠标触发图标事件
                    symbol: 'image://img/top1.svg'
                },
            ]
        })
    }
    展开全文
  • 地图操作关于放大缩小定位搜索等功能,其中最重要的是不管哪个操作,都是对echart的设置,具体函数如下:function localizeMap(center,zoom){ var option = echartMap.getOption() if(center&¢er.length==2) ...

    地图操作关于放大缩小定位搜索等功能,其中最重要的是不管哪个操作,都是对echart的设置,具体函数如下:

    function localizeMap(center,zoom){
        var option = echartMap.getOption()
        if(center&&center.length==2)
            option.series[0].center=center
    
        if(zoom>=4.4)
            option.series[0].zoom=zoom
    
        echartMap.setOption(option)
        setTimeout("keepHighlight()",100)
    }

    首页点击放大

    function mapAmplification(center){
        var option = echartMap.getOption()
        var zoom=option.series[0].zoom+SCALE
        localizeMap(center?center:undefined,zoom)
    }

    首页点击缩小

    function mapNarrow(center){
    var option = echartMap.getOption()
    var zoom=option.series[0].zoom-SCALE
    if(zoom>=4.4)
    localizeMap(center?center:undefined,zoom)
    }

    首页点击定位

    function mapLocalizeInit(){
        var defCenter=[104, 37]
        var defZoom=4.4
        var option = echartMap.getOption()
        if(option.series[0].center[0]!=defCenter[0]||option.series[0].center[1]!=defCenter[1]||option.series[0].zoom!=defZoom)
            localizeMap(defCenter,defZoom)
    }

    首页点击搜索

    实现功能:
    1.默认不显示搜索栏,点击按钮自动缩放弹出搜索栏
    2.模糊匹配,输入单个字母或文字即可出现下拉框进行查询
    3.点击查询的省份即可在地图定位坐标
    以下是实现点击按钮出现搜索栏

     $("#left_bottom_options_search").on("click", function () {
           // $(".top_search").width("10rem")
           if ($(".top_search").attr("data-open")=="no") {
    
                $("#search_con").val($.i18n.prop('com_zte_lte_processingpoorqualitregion_header_poorqualitregion_inputvalue'))
            $(".top_search").animate({width:'11rem'},300);
            $(".top_search").attr("data-open","open")
        }else{
            $(".top_search").animate({width:'0rem'},300);
            $(".top_search").attr("data-open","no")
        }
        })

    以下是模糊搜索

        //搜索
        $("#search_con").on("focus", function () {//获得焦点清空值
            $("#search_con").val("")
            $(".ui-helper-hidden-accessible").hide();
            //模糊匹配控件跟时间控件冲突,会导致每次选择时间后无法执行选中搜索功能,所以使用及时加载的方式
            try{
                if(autoComplete)
                    $( "#search_con" ).autocomplete( "destroy" )
    
            }catch (e){
                console.log(e)
            }
             autoComplete = $("#search_con").autocomplete({//模糊匹配
                autoFocus: true,
                messages: {
                    noResults: $.i18n.prop('com_zte_lte_processingpoorqualitregion_cellsum_homepage_noresult'),
                    results: function() {return ""}
                },
                response: function () {
                    $(".ui-helper-hidden-accessible").show();
                },
                source: allProvinceNames,
                select:function(params1,params2){//选中搜索
                    provinceSearch(params2.item.value);
                    $(".ui-helper-hidden-accessible").hide();
                }
            })
            $(".ui-helper-hidden-accessible").hide();
        })
        $("#search_con").on("blur", function () {//空值恢复
            if ($("#search_con").val() == "") {
                $("#search_con").val($.i18n.prop('com_zte_lte_processingpoorqualitregion_header_poorqualitregion_inputvalue'))
            }
        })

    以下点击搜索名称直接可定位

     $("#search_con").bind('keypress',function(event){//回车搜索
            if(event.keyCode == 13)
                provinceSearch($("#search_con").val())
        })
        $(".search_btn").on("click", function () {//点击搜索
            provinceSearch($("#search_con").val())
        })

    搜索函数:

    function provinceSearch(key){
        provinceHighlight(key)
        if(highlightProvinceParams){
            if("pro".indexOf(thisPage)==0)
                myExports.showProvince(highlightProvinceParams.name)
            else
                detaillDataQuery(highlightProvinceParams.code)
        }
    
    }

    地图高亮效果

    function provinceHighlight(key){
        echartMap.dispatchAction({type: 'downplay'})//取消所有高亮
        highlightProvinceParams=undefined
        var provinceParams=getProvinceParams(key)
        //高亮指定的省
        if(provinceParams){
            highlightProvinceParams=provinceParams
            //省份定位
            var option = echartMap.getOption()
            option.series[1].data=[highlightProvinceParams.offset]
            echartMap.setOption(option)
    
            echartMap.dispatchAction({type: 'highlight',
                seriesIndex: 0,
                name: highlightProvinceParams.name})
        }
    }

    首页地图渲染的值可配置

    调用后端接口服务获取颜色的值,直接在初始化函数调用这个函数getConfigData即可,然后在地图初始化用保存过来的变量

     //得到配置
        var configData=[];
        var colorArry = [];
        var AreaNum;
        SCALE=0.3
    function getConfigData(){
             $.ajax({
                type: "get",
                async: true,
                url: '/rdk/service/app/vmax_r_poorqualityarea/server/poorQualityOfArea/giscolor',
                async: true,
                data: {modelname:'poorQualityOfArea'},
                success: function (result) {
                    if(result){
                        configData = result.value;
                        colorArry = [configData[4],configData[5],configData[6]];
                        AreaNum={
                            name:$.i18n.prop('com_zte_lte_processingpoorqualitregion_header_poorqualitregion_sorttype'),
                            key:"countAreaId",
                            unit:"",
                            start:Number(configData[0]),
                            min:Number(configData[1]),
                            max:Number(configData[2]),
                            end:Number(configData[3])
                        }
                    }
                }
            })
        }
    展开全文
  • 实现效果1.三个趋势图一起联动显示var myChart = echarts.init(document.getElementById('main'), 'infographic'); myChart.setOption(option = { tooltip:{ trigger: 'axis

    实现效果

    1.三个趋势图一起联动显示

    var myChart = echarts.init(document.getElementById('main'), 'infographic');       
                myChart.setOption(option = {
                    tooltip:{
                        trigger: 'axis',
                        formatter: function (params, ticket, callback) {
                            var titleData1;
                            var titleData2;
                            var titleData3;
                            var color1;
                            var color2;
                            var color3;
                            var time= params[0].name;
                            var titleHTML;
                            for (var i = 0; i < params.length; i++) {
                                titleData = params[i].data;
                               if ( params[i].seriesName==($.i18n.prop('com_zte_lte_processingpoorqualitregion_header_poorqualitregion_poorareacount'))){
                                    titleData1=(isNaN(String(titleData))?"--":(titleData));
                                    color1 = params[i].color;
                                }else if ( params[i].seriesName==($.i18n.prop('com_zte_lte_processingpoorqualitregion_header_poorqualitregion_dealingorder'))){
                                    titleData2=(isNaN(String(titleData))?"--":(titleData));
                                    color2 = params[i].color;
                                }else if ( params[i].seriesName==($.i18n.prop('com_zte_lte_processingpoorqualitregion_header_poorqualitregion_overtimeorder'))){
                                    titleData3=(isNaN(String(titleData))?"--":(titleData));
                                    color3 = params[i].color;
                                }
    
                            }
                            titleHTML=time+'<br><div style="display:inline-block;height:10px;width:10px;border-radius:5px;background-color:' + color1 + '"></div>&nbsp;&nbsp'
                                    + $.i18n.prop('com_zte_lte_processingpoorqualitregion_header_poorqualitregion_poorareacount')+' : ' +  titleData1+'<br><br>'
                                    +time+'<br><div style="display:inline-block;height:10px;width:10px;border-radius:5px;background-color:' + color2 + '"></div>&nbsp;&nbsp'
                                    +$.i18n.prop('com_zte_lte_processingpoorqualitregion_header_poorqualitregion_dealingorder')+ ':' +  titleData2+'<br><br>'
                                    +time+'<br><div style="display:inline-block;height:10px;width:10px;border-radius:5px;background-color:' + color3 + '"></div>&nbsp;&nbsp'
                                    +$.i18n.prop('com_zte_lte_processingpoorqualitregion_header_poorqualitregion_overtimeorder')+ ':' +  titleData3;
                            return titleHTML;
                        }
    /*position: function (pos, params, dom, rect, size) {
          // 鼠标在左侧时 tooltip 显示到右侧,鼠标在右侧时 tooltip 显示到左侧。
                 var obj = {top: 60};
                       obj[['left', 'right'][+(pos[0] < size.viewSize[0] / 2)]] = 5;
                             return obj;
                               } */
    //position: ['50%', '50%']   
                    }, 
    
                    axisPointer: {
                        link: {xAxisIndex: 'all'},
                        label: {
                            backgroundColor: '#777'
                        }
                    },
    
                    grid: [
                    {               
                        height: '20%',
                        top:'16%'
                    }, {                
                        top: '49%',   //44
                        height: '20%'  //20
                     },
                     {              
                        top: '80%',   //44
                        height: '20%'  //20
                     }
    
                    ],  
    
                    xAxis:[{
                        gridIndex: 0,//定义图标所在位置,不给默认为0,即第一个,而且必须按照从小到大的
                        type : 'category',
                        position:'bottom',
                        axisLine:{
                            lineStyle:{//坐标轴颜色
                                type:'dashed',
                                color:"#FFFFFF"
                            }
                        },  
                        splitLine:{
                            show:false,//网格是否显示
                            interval: 0,
                            lineStyle:{
                                type:'dashed'
                            }
                        },
                        axisLabel: {
                            show: true,
                            interval: 2,
                            textStyle: {
                                color: '#fff'
                            }
                        },
                        data : chartData.data1.x
                    },{
                        gridIndex: 1,
                        type : 'category',  
                        position:'bottom',
                        axisLine:{
                            lineStyle:{
                                type:'dashed',
                                color:"#FFFFFF"
                            }
                        },  
                        splitLine:{
                            show:false,//网格是否显示
                            interval: 0,
                            lineStyle:{
                                type:'dashed'
                            }
                        },
                        axisLabel: {
                            show: true,
                            interval: 2,
                            textStyle: {
                                color: '#fff'
                            }
                        },
                        data : chartData.data2.x
                    },{
                        gridIndex: 2,
                        type : 'category',  
                        position:'bottom',
                        axisLine:{
                            lineStyle:{
                                type:'dashed',
                                color:"#FFFFFF"
                            }
                        },  
                        splitLine:{
                            show:false,//网格是否显示
                            interval: 0,
                            lineStyle:{
                                type:'dashed'
                            }
                        },
                        axisLabel: {
                            show: true,
                            interval: 2,
                            textStyle: {
                                color: '#fff'
                            }
                        },
                        data : chartData.data3.x
                    },
                    {
                        gridIndex: 0,//定义图标所在位置,不给默认为0,即第一个,而且必须按照从小到大的
                        type : 'category',
                        position:'top',
                        axisLine:{
                            lineStyle:{//坐标轴颜色
                                type:'dashed',
                            }
                        },  
                        splitLine:{
                            show:false,//网格是否显示
                            interval: 0,
                            lineStyle:{
                                type:'dashed'
                            }
                        },
                        // axisLabel: {
         //                    show: true,
         //                    textStyle: {
         //                        color: '#fff'
         //                    }
         //                },
                        // data : chartData.data1.x
                    },{
                        gridIndex: 1,
                        type : 'category',  
                        position:'top',
                        axisLine:{
                            lineStyle:{
                                type:'dashed',
                                color:"#FFFFFF"
                            }
                        },  
                        splitLine:{
                            show:false,//网格是否显示
                            interval: 0,
                            lineStyle:{
                                type:'dashed'
                            }
                        },
                        // axisLabel: {
         //                    show: true,
         //                    textStyle: {
         //                        color: '#fff'
         //                    }
         //                },
                        // data : chartData.data2.x
                    },{
                        gridIndex: 2,
                        type : 'category',  
                        position:'top',
                        axisLine:{
                            lineStyle:{
                                type:'dashed',
                                color:"#FFFFFF"
                            }
                        },  
                        splitLine:{
                            show:false,//网格是否显示
                            interval: 0,
                            lineStyle:{
                                type:'dashed'
                            }
                        },
                        // axisLabel: {
         //                    show: true,
         //                    textStyle: {
         //                        color: '#fff'
         //                    }
         //                },
                        // data : chartData.data3.x
                    }],
    
    
                    yAxis : [{
                        type : 'value', 
                        axisLabel:{
                            formatter:'{value}',
                            show: true,
                                textStyle: {
                                    color: '#fff'
                                }
                        },  
                        axisLine:{
                            lineStyle:{
                                type:'dashed'
                            }
                        },
                        splitNumber:5,  
                        splitLine:{
                            show:true,
                            lineStyle:{
                                type:'dashed'
                            }
                        },
                        yAxisIndex:0,
                        min:0,
                        max:getMax(chartData.data1.y)
                    },
                    {
                        gridIndex: 1,
                        type : 'value', 
                        axisLabel:{
                            formatter:'{value}',
                            show: true,
                                textStyle: {
                                    color: '#fff'
                                }
                        },   
    
                        axisLine:{
                            lineStyle:{
                                type:'dashed'
                            }
                        },  
                        splitNumber:5,
                        splitLine:{
                            show:true,
                            lineStyle:{
                                type:'dashed'
                            }
                        },
                        min:0,
                        max:getMax(chartData.data2.y)
                     },
                    {
                        gridIndex: 2,
                        type : 'value', 
                        axisLabel:{
                            formatter:'{value}',
                            show: true,
                                textStyle: {
                                    color: '#fff'
                                }
                        },   
    
                        axisLine:{
                            lineStyle:{
                                type:'dashed'
                            }
                        },  
                        splitNumber:5,
                        splitLine:{
                            show:true,
                            lineStyle:{
                                type:'dashed'
                            }
                        },
                        min:0,
                        max:getMax(chartData.data3.y)
                     }
                     ,{
                        type : 'value',
                        gridIndex: 0,
                        position:'right',   
                        axisLine:{
                            lineStyle:{
                                type:'dashed',
                                color:"#FFFFFF"
                            }
                        },
                    },
                    {
                        gridIndex: 1,
                        type : 'value', 
                        position:'right',
                        axisLine:{
                            lineStyle:{
                                type:'dashed',
                                color:"#FFFFFF"
                            }
                        },
                    },{
                        gridIndex: 2,
                        type : 'value', 
                        position:'right',
                        axisLine:{
                            lineStyle:{
                                type:'dashed',
                                color:"#FFFFFF"
                            }
                        },
                    }
    
                    ],  
    
                    series : [
                    {
                        name:chartData.data1.name,
                        type:'line',                         
                        smooth:true,
                        showAllSymbol: true,
                        data:chartData.data1.y
                    },                  
                    {
                        name:chartData.data2.name,
                        type:'line',                         
                        smooth:true, 
                        yAxisIndex:1,
                        xAxisIndex:1,
                        showAllSymbol: true,
                        data:chartData.data2.y
                    },                  
                    {
                        name:chartData.data3.name,
                        type:'line',                         
                        smooth:true, 
                        yAxisIndex:2,
                        xAxisIndex:2,
                        showAllSymbol: true,
                        data:chartData.data3.y
                    }
                    ]
                });
                if(allProvinceNames[0].indexOf(chartData.titleData.province)==-1){
                    analysis_type = 'province'
                    choseProvince=chartData.titleData.province
                    $('.right_info_box_button').removeClass('notAllowed').removeClass('analysisBackgroundNot')
                }
                return myChart;
            }
    展开全文
  • 国际地图制图协会于1995年成立了一个地图可视委员会,并与计算机图形学会开始了“Carto-Project”,研究项目,该项目使图形学技术有效地应用在地理学及地图学领域,为空间信息可视提供了良好的技术支撑。
  • 滴滴国际化项目 Android 端演进

    千次阅读 2016-12-20 11:29:11
    滴滴国际化目前有着一些不同于...本文为滴滴出行技术专家 吴更新在 MDCC 2016 移动开发者大会上的演讲,主要介绍了滴滴国际化地图选型、地图扩展适配、网络相关优化、项目整体技术拆分、演进方面的经验。 PPT 下...

    滴滴国际化目前有着一些不同于国内打车的特殊场景——国内用户拿着国产手机出国打车。国内地图、Google 地图均没法用;手机移动漫游网络太慢;同时需要对接不同合作公司的司机运力,这是国际化客户端项目面临的主要问题。本文为滴滴出行技术专家 吴更新在 MDCC 2016 移动开发者大会上的演讲,主要介绍了滴滴国际化在地图选型、地图扩展适配、网络相关优化、项目整体技术拆分、演进方面的经验。

    PPT 下载地址:https://github.com/MDCC2016/Android-Session-Slides
    视频观看地址:http://edu.csdn.net/course/detail/3094

    目前大家用滴滴 App 在美国可以直接打车,不用下载新的 App,现在的滴滴 App 在美国打开就会自动显示海外打车页面。但是,国际化在技术上有一定的特殊性,滴滴国际化业务主要服务于国内用户在国外打车的场景,因此会涉及到与国内业务不太相同的地方,主要体现在地图、网络、运力来源三个方面。前两者很好理解,运力来源中的运力主要是指司机。在国内,个人可以注册滴滴司机,但是在国外我们是与合作伙伴合作,由合作方提供运力,所以在不同的国家,会与不同的合作伙伴实现接入。具体如下:

    1. 地图
      地图作为滴滴客户端重要的支持及基础,而目前我们的友商都没有海外的路网数据,国际化我们需要接入新的国外地图提供商。
    2. 对接不同的运力
      目前滴滴国际化是与海外投资的伙伴进行合作,比如美国打车跟 Lyft 合作。
    3. 漫游网络
      目前国际化的主要用户场景还是国内用户出国打车,这时用户是用国内手机和运营商海外漫游接入网络。

    以上的三个特殊性决定着我们需要在技术上的差异,接下来将围绕地图模块、漫游网络、多业务接入项目演进详细展开。

    地图

    这部分主要包括两大问题:地图选型、地图切换。

    1. 地图选型

    滴滴是个重度依赖地图的 App,而目前我们的友商及大部分国内地图提供商都没有海外的路网数据。我们前期针对的场景是国内用户海外打车,Google Map 依赖 Google Play Service,国内手机几乎都没有这个 Service,即便安装了 Google Play Service 部分手机也无法运行,另外即便都有了,漫游网络也不能访问 Google Map,所以最靠谱的 Google Map 一开始便被排除。

    另外国内有些 App 在海外用了 Google Map,不过是通过中转下发地图切片的方式完成的,但滴滴对于地图各方面的要求都很高,我们必须找到一个合适的国外地图。

    (1) 海外地图选型考察点

    我们对地图强依赖,有些定制需求,如:很多 Marker 并且添加后需要修改、画圆并可以动态调整半径等等。

    国外可用地图数据源主要有 OpenStreetMap、Here、Tomtom,OpenStreetMap 是个开源的地图数据源,类似维基百科的模式,所以数据很全很新,甚至超过 Google Map,但不可避免会有些脏数据,前期的话主要是针对大城市,OpenStreetMap 的数据可以满足需求。但因为涉及到异地跨时区沟通,所以希望技术支持力度够大。

    性能方面则包括地图启动时间、渲染速度、前端响应速度、后端响应速度。

    在开始国际化前,当时滴滴的安装包就已经很大了,基本是国内主流 App 之首(当然现在滴滴 App 已经挺小了),所以我们希望新的地图够小。

    (2) 海外地图全面对比

    这次我们调研了 Mapbox、Nutiteq、Here、Tomtom、Bing 共五款海外地图。其中:

    • Bing 没有 Android 版;
    • Tomtom 有很古老的 Android 版,但功能过于简单,文档又几乎没有;
    • Here SDK 高达 40M,与他们沟通后,精简也只能到 25M,这个大小是绝对接受不了的;

    所以我们重点集成和测试的是 Mapbox 和 Nutiteq 这两家地图供应商。

    Mapbox 和 Nutiteq 的功能和性能都满足我们需求,地图数据源也都是以 OSM(OpenStreetMap) 为主。Mapbox 的 API 设计和国内地图类似,都是向 Google Map 靠拢,所以上手简单,并且整个 SDK 都是开源的,地图的样式也更美观些,而 Nutiteq 的地图底层设计比较独特,API 用法很不寻常,这也给我们接入带来了很大的麻烦。

    Mapbox 有众多的 Web 用户,包括访问量都不低的 Foursquare、Pinterest 等,但 Android 端用户并不多;Nutiteq 的 Android 用户多些,但整体量也不是很大,不过我们并没有更好的选择,而且前期我们的量也不会很大,所以他们都在可接受范围内。

    综合下来看的话,我们是更倾向于 Mapbox,不过 Mapbox 只能通过 GitHub Issues 和邮件反馈问题,反应很慢;Nutiteq 可以 Skype 沟通,效率很高。为了保险起见,Mapbox 和 Nutiteq 都做了全面接入和测试,最终证明这样是有用的。

    跟多数 App 一样,为了使得包更小,我们的主工程配置了 abiFilter “armeabi”,仅打 armabi 的 so,而 Mapbox 的 armeabi so 无法跑在 armv7 机器上,前期集成测试我们通过修改 Gradle 脚本在编译时 copy so 的方式让测试通过,而 Mapbox 一直不愿意改,国内市场又不支持 Google 的 Apk Splits 机制,所以最终放弃选择 Nutiteq。

    后话:Mapbox 最新版已经解决了这个问题,而且国内有相关的市场人员,沟通起来也顺畅多了。

    2. 地图切换

    用不了 Google Map 带来一个要求,我们选择的地图必须支持多国家,并且在设计时要支持以后不同地图任意切换。是的,即地图和 App 弱依赖。针对这个问题我们设计了地图隔离层。总体设计如下:

    上图第二层 MapSDK 是地图的标准 API 层,App 只与此层打交道,标准层的 API 设计以 Google Map API 为标准。

    第三层 Adapter 层是具体地图到标准 API 的适配实现层。每个地图都有个 Adapter,负责将地图 API 转换成标准 API。

    将原来的 App 与三方地图直接依赖改为 App 依赖表示标准 API 的 MapSDK 层,由 MapSDK 通过具体的 Adapter 调用三方 SDK,这样地图切换只需要替换依赖的 Adapter 即可,其他地方无需改动。

    新的设计后编译依赖关系如下:

    App 依赖 Map Adapter,Map Adapter 依赖我们的 MapSDK 和三方的 Map SDK。当我们需要更换三方地图 SDK 时,仅需更换对应的 Map Adapter 即可。对于 Android,build.gradle 中更换依赖即可。

    3. 新的地图模块设计的好处

    1. 解耦,切换成本低
      这个上面已经介绍,再也不会因为换了地图牵一发而动全身。
    2. 学习成本低
      业务开发人员只需要熟悉标准 MapSDK API 即可,不用了解其他地图的具体使用,时间成本降低。
    3. 通用
      适用于所有 App,以后新增 App,可直接使用之前成型的 Adapter。

    4. 地图切换实现的注意事项

    1. 所有 API 适配
      理论上 MapSDK 应为地图所有 API 最大集,实际可以根据情况先去做所需功能的定义和适配。
    2. 标尺
      需要统一标尺,如缩放尺度、相同坐标系等。
    3. 未支持 API 处理
      因为标准层的 MapSDK 是地图功能最大集,所以不可避免某些三方地图不支持 MapSDK 定义的功能。比如根据一组点缩放这个功能,其对应的 Adapter 在实现这个功能时如果是 Debug 模式则抛异常,Release 模式则空实现。

    还有如 MapSDK 的 API 规范前面已经介绍过以 Google Map API 为标准。另 Adapter 有具体的开发规范要求。

    漫游网络

    前面介绍过我们初期针对的是国内用户海外打车场景,这时用户是用国内手机和运营商海外漫游接入网络,所以需要针对网络访问进行优化。

    一般漫游网络流程如下图:

    用户由海外运营商接入国内运营商,再通过公网(有墙)访问 Web。我们的服务器部署在 AWS 上,用户海外漫游打车网络流程如下图:

    由于公网访问 AWS 非常慢,我们添加了海外专线,优化后用户海外漫游打车网络流程如下图:

    用户先访问到国内的中转服务器,中转服务器再通过海外专线访问 AWS。

    这个过程中客户端要做的工作包括:

    • 拉取中转服务器域名列表
    • 使用中转服务器域名列表中域名访问,出错则用原始域名降级重试
    • 定时及推送更新域名列表
      这里域名顺序由服务端自己负载均衡,返回的中转服务器域名列表是中转服务器域名还是直接海外域名也由服务器决定。

    Android 项目演进

    1. 原有模式

    之前国际化业务的工程是很简单的方式,所有业务、组件、工具放在一起,根据具体包名划分:

    这个在早期问题不大,并且开发起来快速方便,但随着更多业务接入,如我们前面说过的新的国家运力接入,问题就日益明显,包括:

    • 组件之间耦合
      虽然已经划分包名,但依然可以互相调用,组件间依赖关系不清,甚至有循环依赖。
    • 添加新业务不便
    • 开发问题
      规模越来越大致提交冲突可能性变大。

    2. SDK 工程提取

    将原工程整体拆分为业务工程和 SDK 工程,单业务工程直接依赖 SDK,可独立开发、独立运行、独立打包。如下:

    这样在接入新的业务后,总体项目结构如下图:

    每个业务作为单独工程,共用组件、工具、业务统一到 SDK 层中。集成工程负责集成 Lyft、Ola、GrabTaxi 项目,所有业务项目提供 AAR,由集成工程整体打包对外发布。

    3. SDK 工程组件化拆分

    为了解决组件之间耦合,防止后续问题加剧,同时方便协同开发和更好的复用,将 SDK 工程组件化拆分如下:

    SDK 整体拆分为 Business Library 和 Util Library 两大部分,主要依据是是否可以独立于我们业务,他们间不允许反向依赖。每个部分包含若干组件,每个组件都以 Module 形式存在。

    Business Library 为通用业务层,包含通用业务组件,如平滑移动、上车点、定位、地理信息、打点、网络封装。其中 CommonBusiness 存放暂时通用、但尚不足以作为一个单独组件的公共业务,以后可能独立出来,注意包名规范方便未来独立。Util Library 为工具库,大致分为 View 和 Util,DidiSDK 为滴滴 App 整体通用组件包,包含通用的图片缓存、网络请求、基础登陆组件等等。

    4. SDK 组件化拆分后依赖关系图

    通过上图我们可以发现即便只是 Business Library 层,组件也根据依赖关系划分为明显的上下层。

    5. SDK 组件化划分事项

    (1) 单一及开闭原则

    每个模块只代表一个功能模块或一个公共业务,对于个性化或定制功能以接口形式对外开放。

    注:目前 CommonBusiness 模块暂时作为国际化 SDK 整体集成打包的模块,即国际化 SDK 项目中的 sdk Module,后续当其中某个公共业务足够成为一个模块时可继续拆分出来。

    (2) 拆分粒度

    项目的演进是不断进行的,没必要将每个细小组件都拆分出来,这样不仅增加了项目的复杂度,同时也会影响编译时间。

    先根据实际需要拆分必要的组件,太小暂不足以独立的组件可以在以后不断进行的重构中根据需要拆分。如上面的 CommonBusiness 模块,当然需要保持一定的规范方便以后拆分。

    (3) 依赖关系

    通过依赖图整理依赖关系,防止重复依赖,同时看出沉淀关系。

    • Util Library 不能反向依赖 Business Library;
    • Business Library 除了基础部分,如 Net、Geo、EventTrack 外,其他部分尽量不要相互依赖;
    • Business Library 中 Net、Geo、EventTrack 不允许反向依赖其他模块。

    (4) 开发规范

    为了保证扩展性及方便以后继续拆分:

    • 所有业务包名以 com.didi.{xx}.sdk.{businessName} 开头;
    • CommonUtil 模块中所有工具包名以 com.didi.{xx}.sdk.util.{utilName} 开头;
    • CommonView 模块中所有 View 包名以 com.didi.{xx}.sdk.view.{viewName} 开头;

    (5) 组件间通信

    放弃原来造成耦合严重的 EventBus,改用原生的通信方式,包括原生 (startActivityForResult) 、内部广播、回调等。

    6. SDK 组件化项目整体设计图

    其中虚线部分为 SDK 层。

    7. 组件化拆分后的好处

    • 组件间解耦
    • 业务并行开发、测试
    • 组件单独测试

    了解最新移动开发相关信息和技术,请关注 mobilehub 公众微信号(ID: mobilehub)。

    mobilehub

    展开全文
  • 国际地图制图协会于1995年成立了一个地图可视委员会,并与计算机图形学会开始了“Carto-Project”,研究项目,该项目使图形学技术有效地应用在地理学及地图学领域,为空间信息可视提供了良好的技术支撑。
  • 关于GMTCGMTC全球大前端技术大会是由InfoQ举办的年度前端技术盛会,主要面向各行业对前端、移动感兴趣的中高端技术人员。6月20日-21日,GMTC将在北京国际会议...
  • 浅谈地图可视

    2020-05-01 22:56:15
    地图可视在我们生活中无处不在,开车导航,寻找某个地方需要导航等,下面浅谈一下地图的一些知识及应用。 当前互联网地图的坐标系现状 地球坐标 (WGS84) 国际标准,从专业GPS 设备中取出的数据的坐标系 国际...
  • 地图是人类文化的杰作,它融科学、艺术于一体,作为描述、研究人类生存环境的一种信息载体是人类生产与生活中不可缺少的一种工具。”这是陈述彭院士为《中国地图学年鉴》作序的...地图地图可视的结果,地图...
  • 数据可视随笔 -- 地图百度地图 - - 结合echarts实现数据展示高德地图 - - 最友好的地图Google地图 - - 中英文切换地图三大地图汇总 - - 淘金 百度地图 - - 结合echarts实现数据展示 实现效果图: 引入第三方插件...
  • 命令进入项目目录,执行以下命令安装vue 国际化插件vue-i18n npm install vue-i18n --save 2. 项目增加国际化翻译文件 在项目的src下添加lang文件夹增加中文翻译文件(zh.js)以及英文翻译文件(en.js),里面...
  • 相关阅读: ...滴滴国际化目前有着一些不同于国内打车的特殊场景——国内用户拿着国产手机出国打车。国内地图、Google 地图均没法用;手机移动漫游网络太慢;同时需要对接不同合作公司的司机运力,这是国际
  • 本文章篇幅略长,内容有点多 大佬可根据目录选择性查阅 新人可一步步来阅读 1 前言 ...有线图、柱状图、地图,类似于数据可视的方式。 本人之前从未接触过Echarts,然后需要2周拿出成果,有点慌
  • vue地图可视 ArcGIS篇(3)

    千次阅读 2018-10-31 04:26:05
    本次实例中采用ArcGIS for javascript3.24版本,由于版本3与4在API等存在较大区别,就不一一列举,详细区别看官方解释 arcgis for js4.7版本能够自动创建layer、graphs等类,而不像3.24版本需要在图形渲染前重新new ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 2,699
精华内容 1,079
关键字:

地图渲染国际化