65.9K
CodeProject 正在变化。 阅读更多。
Home

同步 Google Earth 和地图视图

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.94/5 (11投票s)

2014年3月2日

CPOL

13分钟阅读

viewsIcon

40940

downloadIcon

2262

使用 jQuery 同步 Google Earth 和地图视图。

引言

主要目标是同时显示谷歌地球和谷歌地图。也就是说,当一个视图平移或缩放时,另一个视图会在中心视图位置和高度/缩放级别方面自动同步。对于平移和缩放,都需要避免出现抖动或振荡等不良行为。当从谷歌地球或谷歌地图放大或缩小时,缩放也需要对称。

另一个目标是允许在谷歌地球和谷歌地图中同时加载可变数量的标记。标记集通过 Ajax jQuery 调用从 Web 服务器获取。在客户端 jQuery 代码执行期间,可以随时动态加载新的标记集。可以调整视图以自动以优化的方式显示所有标记(即,能够显示所有已加载标记的最低缩放级别)。

下图显示了我们希望的显示效果

左上角有一个位置下拉列表。选择一个位置后,谷歌地球和谷歌地图将定位在该位置。此外,右侧的按钮将以最佳缩放级别/高度显示所有位置。在中上方,一个链接按钮允许谷歌地球和谷歌地图可选地自动同步。在右上角,按钮允许分别显示或隐藏谷歌地球、谷歌地图和驾车路线。

在顶部的位置和按钮控件下方,左侧显示谷歌地球,右侧显示谷歌地图。驾车路线可以选择显示在谷歌地图的右侧。在谷歌地球和谷歌地图的正上方,显示了中心点的位置以及一组代表适用图层的复选框。

背景

谷歌地球是一个虚拟地球仪,它是地球的三维软件模型。谷歌地球使用摄像机的概念,以及摄像机的位置和摄像机注视的方向来推导和显示地球的当前视图。因此,用户可以看向除天顶(即,摄像机正下方的地球点)以外的方向。

谷歌地图是一个 Web 地图应用程序,它使用二维卫星图像和三维地球视图。谷歌地图在离散的地图缩放级别显示天顶视图,其数量可能因地理位置而异。此外,谷歌地图还具有许多有用的图层,用于交通、公交、自行车,并且可以提供地点之间的路线。

谷歌地球和谷歌地图都具有一些相似的功能,例如使用纬度/经度坐标对的地理标记。然而,它们各自的优势相结合,可以提供比单独使用谷歌地球和谷歌地图所带来的更多好处。

Using the Code

为了帮助缓解在同步谷歌地球与地图或反之的抖动问题,我们将实现一个计时器。这还允许在较长的平移操作期间,一个视图可以定期与另一个视图同步。有了计时器,我们可以定位谷歌地球或谷歌地图,而不必担心同步问题。我们还将对各自的谷歌地球和谷歌地图坐标进行四舍五入,以减轻在未使用完全相同的精度时出现的查看问题。

此外,我们将定义一个与地图缩放级别相对应的高度数组,以便在两个视图之间实现对称的缩放。缩放级别的数量因地理位置而异。因此,我们的数组需要包含所有可能的值,这些值随后将由谷歌地图适当地进行调整。

对于此实现,我们将编写一个完全独立于标记的 jQuery 脚本。我们还将使用 Google Maps API v3 和当前的 Google Earth API v1。应用程序本身是 ASP.NET MVC 4,但 jQuery 和相关标记独立于应用程序框架,这使得将其轻松、直接地移植到其他 Web 框架成为可能。在很大程度上,本文档讨论的是与所使用的 Web 框架无关的 jQuery 和标记部分。

GoogleEarthMap 实现已成功通过 Internet Explorer 11、Firefox 27 和 Chrome 33 进行测试。Safari 未经测试,但没有理由认为它不适用于 GoogleEarthMap 实现。

通用声明 (GoogleEarthMaps.js)

locations 变量将保存一个数组,其中每个元素是一个地理位置,包含我们用于填充两个视图标记的信息。timerDelay 变量确定了调用窗口的间隔计时器函数之间的毫秒数。precision 变量是我们认为相关的纬度和经度坐标的小数位数,但应小于或等于谷歌地球或地图精度的较小值。其余变量是各种视图和功能的开关。

var locations = null;
var timerDelay = 2000;      // Milliseconds
var precision = 6;          // Significant decimal places
var autoSync = true;
var showEarth = true;
var showMap = true;
var showDirections = false;

接下来,我们将声明两个图像用于我们的链接按钮(即,自动同步或不同步谷歌地球和谷歌地图)。十字准星图像用于谷歌地球和地图,以识别视图的中心点。

var linkImage = '../Images/link.png';
var unlinkImage = '../Images/unlink.png';
var crosshairImage = 'http://maps.google.com/mapfiles/kml/shapes/cross-hairs.png'; // Standard image

altZoomList 数组声明了每个可能缩放级别的 HAE(高于椭球体)高度(米)。

var altZoomList = [ // Altitude <-> Zoom level
    30000000, 24000000, 18000000, 10000000, 4000000, 1900000, 1100000, 550000, 280000,
    170000, 82000, 38000, 19000, 9200, 4300, 2000, 990, 570, 280, 100, 36, 12, 0 ];

AltToZoom 函数将高度转换为缩放级别。由于数组以最高高度值开头,因此返回第一个数组索引(即,缩放级别),其中给定高度大于数组高度减去数组高度与下一个高度之间距离的一半。对于缩放级别较少的位置,谷歌地图将随后调整此值。ZoomToAlt 函数基于相同的通用高度值数组反转之前的转换。

function AltToZoom(alt) {
    /// <summary>Converts an altitude to a zoom level
    /// <param name="alt" />Altitude in meters
    /// <returns>Zoom level
    for (var i = 0; i < 22; ++i) {
        if (alt > altZoomList[i] - ((altZoomList[i] - altZoomList[i+1]) / 2)) return i;
    }
    return 10;
}

function ZoomToAlt(zoom) {
    /// <summary>Converts a zoom level to an altitude
    /// <param name="zoom" />Zoom level
    /// <returns>Altitude in meters
    return altZoomList[zoom < 0 ? 0 : zoom > 21 ? 21 : zoom];
}

DecRound 函数将给定值四舍五入到指定的小数位数。

function DecRound(val, n) {
    /// <summary>Rounds a number to a given decimal places
    /// <param name="val" />Number to round
    /// <param name="n" />Significant decimal places
    /// <returns>Rounded number
    var factor;
    factor = Math.pow(10, n);
    return (Math.round(val * factor) / factor);
}

LatDecToDegMinLngDecToDegMin 函数分别将纬度和经度值转换为度分格式字符串。值以十进制度给出,并转换为表示度分符号的字符串。

function LatDecToDegMin(decLatitude) {
    /// <summary>Format a degree minute notation from a latitude
    /// <param name="decLatitude" />Decimal latitude
    /// <returns>Degree minute notation string
    var intDegree;
    var decMinute;
    var strLatitude = 'N';
    if (decLatitude < 0) {
        strLatitude = 'S';
        decLatitude = decLatitude * -1;
    }
    intDegree = Math.floor(decLatitude);
    decMinute = (decLatitude - intDegree) * 60;
    decMinute = DecRound(decMinute, 3);
    strLatitude = String(intDegree) + '\u00B0 ' + String(decMinute) + '\u2032 ' + strLatitude;
    return strLatitude;
}

function LngDecToDegMin(decLongitude) {
    /// <summary>Format a degree minute notation from a longitude
    /// <param name="decLongitude" />Decimal longitude
    /// <returns>Degree minute notation string
    var intDegree;
    var decMinute;
    var strLongitude = 'E';
    if (decLongitude < 0) {
        strLongitude = 'W';
        decLongitude = decLongitude * -1;
    }
    intDegree = Math.floor(decLongitude);
    decMinute = (decLongitude - intDegree) * 60;
    decMinute = DecRound(decMinute, 3);
    strLongitude = String(intDegree) + '\u00B0 ' + String(decMinute) + '\u2032 ' + strLongitude;
    return strLongitude;
}

SetAutoSyncImage 函数根据 autoSync 开关更改链接按钮的图像以及谷歌地球和谷歌地图视图周围的边框。

function SetAutoSyncImage() {
    /// <summary>Set the auto sync image and borders
    $('#syncAuto').attr('src', (autoSync ? linkImage : unlinkImage));
    $('#googleEarth').css('border-color', (autoSync ? 'blue' : 'transparent'));
    $('#googleMaps').css('border-color', (autoSync ? 'blue' : 'transparent'));
}

谷歌地球 (GoogleEarthMaps.js)

以下代码声明了主要的谷歌地球实例 ge 和中心标牌 cp。声明了一个 placemarks 数组,用于包含代表 locations 数组元素的其他标牌。加载了最新的谷歌地球主要版本 1,并调用 InitGoogleEarth 函数来创建我们的谷歌地球实例。

var ge = null;
var cp = null;
var placemarks = [];
google.load('earth', '1');


function InitGoogleEarth() {
    /// <summary>Creates an instance of Google Earth
    google.earth.createInstance('googleEarth', InitGECallback);
}

InitGECallback 函数初始化我们的谷歌地球实例,设置导航控件,并启用街景。然后使用 crosshairImage 创建中心点标牌,并连接 UpdateGEStatus 事件侦听器以检测视图更改。由于这是一个初始化回调函数,并且位置可能已经加载,因此通过调用 InitGELocations 来创建其他标牌。最后,将各种谷歌地球图层处理程序连接到它们各自的复选框。

function InitGECallback(object) {
    /// <summary>Callback after Google Earth instance has been created
    /// <param name="object" />Google Earth instance
    ge = object;
    ge.getWindow().setVisibility(true);
    ge.getNavigationControl().setVisibility(ge.VISIBILITY_SHOW);
    ge.getNavigationControl().setStreetViewEnabled(true);

    var icon = ge.createIcon('');
    icon.setHref(crosshairImage);
    var style = ge.createStyle('');
    style.getIconStyle().setIcon(icon);

    cp = ge.createPlacemark(''); // Center point placemark
    cp.setStyleSelector(style);
    var pt = ge.createPoint('');
    pt.setLatitude(0);
    pt.setLongitude(0);
    cp.setGeometry(pt);
    ge.getFeatures().appendChild(cp);

    google.earth.addEventListener(ge.getView(), 'viewchange', function () {
        UpdateGEStatus();
    });

    InitGELocations();

    $('#chkGETerrain').change(function (event) {
        ge.getLayerRoot().enableLayerById(ge.LAYER_TERRAIN, this.checked);
    });

    $('#chkGEBorders').change(function () {
        ge.getLayerRoot().enableLayerById(ge.LAYER_BORDERS, this.checked);
    });

    $('#chkGERoads').change(function () {
        ge.getLayerRoot().enableLayerById(ge.LAYER_ROADS, this.checked);
    });
}

InitGELocations 函数清除任何先前定义的地点标牌,并为 locations 数组中的所有元素创建新的标牌。

function InitGELocations() {
    /// <summary>Clear placemarks and initialize new placemarks from the locations array
    if (locations == null || ge == null) return;

    var features = ge.getFeatures();

    // Clear any placemarks
    for (var i = 0; i < placemarks.length; ++i) {
        features.removeChild(placemarks[i]);
    }
    placemarks = [];

    $.each(locations, function(index, loc) {
        var styleMap = ge.createStyleMap('');
        var mapHighlight = ge.createStyle('');
        mapHighlight.getBalloonStyle().setText(loc.Name + '\n' + loc.Address);
        styleMap.setHighlightStyle(mapHighlight);

        var pt = ge.createPoint('');
        pt.setLatitude(loc.Latitude);
        pt.setLongitude(loc.Longitude);

        var pm = ge.createPlacemark('');
        pm.setStyleSelector(styleMap);
        pm.setGeometry(pt);
        features.appendChild(pm);

        placemarks.push(pm);
    });
}

UpdateGEStatus 函数更新谷歌地球的状态显示。状态显示包括中心点标牌的纬度、经度和高度。

function UpdateGEStatus() {
    /// <summary>Update the Google Earth status display
    var camera = ge.getView().copyAsCamera(ge.ALTITUDE_RELATIVE_TO_GROUND);
    var pt = cp.getGeometry();
    pt.setLatitude(camera.getLatitude());
    pt.setLongitude(camera.getLongitude());
    cp.setGeometry(pt);

    var status = $('#googleEarthStatus');
    status.text('Lat: ' + LatDecToDegMin(camera.getLatitude()) + ', Lng: ' + 
    LngDecToDegMin(camera.getLongitude()) + ', Alt: ' + String(DecRound(camera.getAltitude(), 1)) + ' m');
}

谷歌地图 (GoogleEarthMaps.js)

以下代码声明了主要的谷歌地图实例 gm 以及用于路线服务 dirService 和路线渲染器 dirDisplay 的变量。声明了一个 markers 数组,用于包含代表 locations 数组元素的标记,并声明了与各种免费地图图层对应的切换变量。最后,声明了最后一个中心位置和最后一个缩放级别的变量,以便我们的计时器函数可以确定哪个视图发生了变化。

var gm = null;
var dirService = null;
var dirDisplay = null;
var markers = [];
var trafficLayer = null;
var transitLayer = null;
var bicycleLayer = null;
var weatherLayer = null;
var cloudLayer = null;
var lastCenter = null;
var lastZoom = 0;

InitGoogleMaps 函数初始化我们的谷歌地图实例,并设置路线服务和渲染器。然后使用 crosshairImage 创建中心点标记,并连接 UpdateGMStatus 事件侦听器以检测中心点和缩放更改。最后,将各种谷歌地图图层处理程序连接到它们各自的复选框。

function InitGoogleMaps() {
    /// <summary>Creates an instance of Google Maps
    var mapOptions = {
        mapTypeId: google.maps.MapTypeId.ROADMAP,
        center: new google.maps.LatLng(0, 0),
        zoom: 8
    };

    gm = new google.maps.Map(document.getElementById('googleMaps'), mapOptions);

    dirService = new google.maps.DirectionsService();
    dirDisplay = new google.maps.DirectionsRenderer();

    var marker = new google.maps.Marker({ // Center point marker
        map: gm,
        icon: crosshairImage,
        shape: { coords: [0, 0, 0, 0], type: 'rect' }
    });
    marker.bindTo('position', gm, 'center');

    google.maps.event.addListener(gm, 'center_changed', function () {
        UpdateGMStatus();
    });

    google.maps.event.addListener(gm, 'zoom_changed', function () {
        UpdateGMStatus();
    });

    $('#chkGMTraffic').change(function () {
        if (this.checked) {
            trafficLayer = new google.maps.TrafficLayer();
            trafficLayer.setMap(gm);
        } else {
            trafficLayer.setMap(null);
            trafficLayer = null;
        }
    });

    $('#chkGMTransit').change(function () {
        if (this.checked) {
            transitLayer = new google.maps.TransitLayer();
            transitLayer.setMap(gm);
        } else {
            transitLayer.setMap(null);
            transitLayer = null;
        }
    });

    $('#chkGMBicycle').change(function () {
        if (this.checked) {
            bicycleLayer = new google.maps.BicyclingLayer();
            bicycleLayer.setMap(gm);
        } else {
            bicycleLayer.setMap(null);
            bicycleLayer = null;
        }
    });

    $('#chkGMWeather').change(function () {
        if (this.checked) {
            weatherLayer = new google.maps.weather.WeatherLayer({
                temperatureUnits: google.maps.weather.TemperatureUnit.FAHRENHEIT
            });
            weatherLayer.setMap(gm);
        } else {
            weatherLayer.setMap(null);
            weatherLayer = null;
        }
    });

    $('#chkGMClouds').change(function () {
        if (this.checked) {
            cloudLayer = new google.maps.weather.CloudLayer();
            cloudLayer.setMap(gm);
        } else {
            cloudLayer.setMap(null);
            cloudLayer = null;
        }
    });
}

InitGMLocations 函数清除任何先前定义的地点标记,并为 locations 数组中的所有元素创建新的标记。

function InitGMLocations() {
    /// <summary>Clear markers and initialize new markers from the locations array
    if (locations == null || gm == null) return;

    // Clear any markers
    for (var i = 0; i < markers.length; ++i) {
        markers[i].setMap(null);
    }
    markers = [];

    $.each(locations, function(index, loc) {
        var marker = new google.maps.Marker({
            position: new google.maps.LatLng(loc.Latitude, loc.Longitude),
            map: gm,
            title: (loc.Name + '\n' + loc.Address)
        });

        markers.push(marker);
    });
}

UpdateGEStatus 函数更新谷歌地图的状态显示。状态显示包括中心点标记的纬度和经度,以及地图的缩放级别。

function UpdateGMStatus() {
    /// <summary>Update the Google Maps status display
    var center = gm.getCenter();

    var status = $('#googleMapsStatus');
    status.text('Lat: ' + LatDecToDegMin(center.lat()) + ', Lng: ' + 
    LngDecToDegMin(center.lng()) + ', Zoom: ' + String(gm.zoom));
}

同步函数 (GoogleEarthMaps.js)

RequestLocationsAjax 函数使用 jQuery 发出异步回发,请求 JSON 格式的位置数组。我们将 JSON 响应设置为 locations 数组,然后重新初始化我们的位置下拉列表。最后,通过分别调用 InitGELocationsInitGMLocations 函数,使用新的 locations 数组重新初始化谷歌地球标牌和谷歌地图标记。

function RequestLocationsAjax() {
    /// <summary>Creates an Ajax request and sets the locations array to the JSON response
    $.getJSON('/Location/GetLocations', { region: '' }, function (json) {
        locations = json;

        var locationList = $('#locationList');
        locationList.empty();

        var docfrag = document.createDocumentFragment();
        $.each(locations, function (index, loc) {
            var option = document.createElement("option");
            option.innerHTML = loc.Name;
            docfrag.appendChild(option);
        });

        locationList.append(docfrag);

        InitGELocations();
        InitGMLocations();
    });
}

SyncEarth 函数将谷歌地球视图的摄像机设置为纬度、经度和高度参数的值。

function SyncEarth(lat, lng, alt) {
    /// <summary>Sync the Google Earth to a geographical location
    /// <param name="lat" />Latitude
    /// <param name="lng" />Longitude
    /// <param name="alt" />Altitude
    /// <returns>The view's camera
    var camera = ge.getView().copyAsCamera(ge.ALTITUDE_RELATIVE_TO_GROUND);

    camera.setLatitude(lat);
    camera.setLongitude(lng);
    camera.setAltitude(alt);

    ge.getView().setAbstractView(camera);
    return camera;
}

SyncMap 函数将谷歌地图的中心位置和缩放级别设置为谷歌地球摄像机的值。特别是,通过调用 AltToZoom 函数从高度派生缩放级别。

function SyncMap(camera) {
    /// <summary>Sync the Google Maps to the geographical location of a camera
    /// <param name="camera" />The view's camera
    gm.setCenter(new google.maps.LatLng(camera.getLatitude(), camera.getLongitude()));
    gm.setZoom(AltToZoom(camera.getAltitude()));
}

SyncLocation 函数首先将谷歌地球同步到当前选定的位置。然后,它将谷歌地图同步到谷歌地球。

function SyncToLocation() {
    /// <summary>Sync the Google Earth and Google Maps to the currently selected location
    var index = $("#locationList")[0].selectedIndex;
    if (index < 0) return;

    var camera = SyncEarth(locations[index].Latitude, locations[index].Longitude, locations[index].Altitude);
    SyncMap(camera);
}

SyncEarthToMap 函数将谷歌地球同步到谷歌地图。特别是,通过调用 ZoomToAlt 函数从缩放级别派生高度。

function SyncEarthToMap() {
    /// <summary>Sync the Google Earth to the Google Maps
    var center = gm.getCenter();
    SyncEarth(center.lat(), center.lng(), ZoomToAlt(gm.zoom));
}

SyncMapToEarth 函数将谷歌地图同步到谷歌地球。

function SyncMapToEarth() {
    /// <summary>Sync the Google Maps to the Google Earth
    SyncMap(ge.getView().copyAsCamera(ge.ALTITUDE_RELATIVE_TO_GROUND));
}

SyncAllLocations 函数确定包含所有当前定义的谷歌地图标记的边界,然后定位地图以显示所有标记。然后调用 SyncEarthToMap 函数将谷歌地球同步到谷歌地图。

function SyncAllLocations() {
    /// <summary>Sync the Google Earth and Google Maps to a bounds that shows all locations
    var bound = new google.maps.LatLngBounds();

    for (var i = 0; i < markers.length; ++i) {
        bound.extend(markers[i].getPosition());
    }

    gm.fitBounds(bound);
    SyncEarthToMap();
}

事件处理程序 (GoogleEarthMaps.js)

GetDirections 函数使用当前选定的位置创建一个驾车路线请求。然后,它调用 dirService 路线服务 route 函数,并将 dirDisplay 路线渲染器设置为驾车路线响应。

function GetDirections(obj) {
    /// <summary>Get the driving directions from a starting address to the selection location
    /// <param name="obj" />Source object instance
    var index = $("#locationList")[0].selectedIndex;
    if (index < 0) return;

    dirDisplay.setMap(gm);
    dirDisplay.setPanel(document.getElementById('divDirections'));

    var request = {
        origin: document.getElementById('fromAddress').value,
        destination: locations[index].Address,
        travelMode: google.maps.DirectionsTravelMode.DRIVING
    };

    dirService.route(request, function (response, status) {
        if (status == google.maps.DirectionsStatus.OK) {
            dirDisplay.setDirections(response);
        }
    });
}

ShowEarth 函数切换谷歌地球视图的显示/隐藏。将触发谷歌地图 resize 事件以调整显示。谷歌地球显示会自动调整。

function ShowEarth(obj) {
    /// <summary>Show or hide the Google Earth
    /// <param name="obj" />Source object instance
    showEarth = !showEarth;

    if (showEarth) {
        $(obj).val('Hide Earth');
        $('#tdEarth').css('display', 'table-cell');
        $('#tdEarthExt').css('display', 'table-cell');
    } else {
        $(obj).val('Show Earth');
        $('#tdEarth').css('display', 'none');
        $('#tdEarthExt').css('display', 'none');
    }

    google.maps.event.trigger(gm, "resize");
}

ShowMap 函数切换谷歌地图视图的显示/隐藏。将触发谷歌地图 resize 事件以调整显示。谷歌地球显示会自动调整。

function ShowMap(obj) {
    /// <summary>Show or hide the Google Maps
    /// <param name="obj" />Source object instance
    showMap = !showMap;

    if (showMap) {
        $(obj).val('Hide Map');
        $('#tdMap').css('display', 'table-cell');
        $('#tdMapExt').css('display', 'table-cell');
    } else {
        $(obj).val('Show Map');
        $('#tdMap').css('display', 'none');
        $('#tdMapExt').css('display', 'none');
    }

    google.maps.event.trigger(gm, "resize");
}

ShowDirections 函数切换谷歌地图路线的显示/隐藏。

function ShowDirections(obj) {
    /// <summary>Show or hide the driving directions
    /// <param name="obj" />Source object instance
    showDirections = !showDirections;

    if (showDirections) {
        $(obj).val('Hide Directions');
        $('#tdDirections').css('display', 'table-cell');
    } else {
        $(obj).val('Show Directions');
        $('#tdDirections').css('display', 'none');
        dirDisplay.setMap(null);
        dirDisplay.setPanel(null);
    }
}

当开关 autoSynctrue 时,TimerFunction 首先检查地图的最后一个中心位置是否已设置,如果未设置,则调用 SyncAllLocations 函数将谷歌地球和谷歌地图定位到显示当前已知的位置,然后从函数返回。所有相关的活动都在 try/catch 中进行,以避免计时器线程中断。

然后,函数检查谷歌地球摄像机和谷歌地图中心位置的四舍五入值是否不同,或者摄像机的计算缩放级别是否与谷歌地图缩放级别不同。如果是,则在谷歌地图的中心位置和缩放级别未改变的情况下,将谷歌地图同步到谷歌地球,否则将谷歌地球同步到谷歌地图。最后,记录最后一个谷歌地图中心位置和缩放级别,为下一次调用此函数做准备。

function TimerFunction() {
    /// <summary>Periodically syncs the Google Earth and Google Maps
    if (!autoSync) return;

    try {
        var camera = ge.getView().copyAsCamera(ge.ALTITUDE_RELATIVE_TO_GROUND);

        if (lastCenter == null) { // Never set
            SyncAllLocations();
            lastCenter = gm.center;
            lastZoom = gm.zoom;
            return;
        }

        var center = gm.getCenter();
        // Determine if syncing is needed
        if (DecRound(camera.getLatitude(), precision) != DecRound(center.lat(), precision) ||
                DecRound(camera.getLongitude(), precision) != DecRound(center.lng(), precision) ||
                AltToZoom(camera.getAltitude()) != gm.zoom) {
            if (center == lastCenter && gm.zoom == lastZoom) // Determine what needs to be synced
                SyncMapToEarth();
            else
                SyncEarthToMap();
        }

        lastCenter = gm.center;
        lastZoom = gm.zoom;
    }
    catch (err) {
    }
}

初始化 (GoogleEarthMaps.js)

文档 ready 函数首先设置链接按钮的图像,然后通过调用 InitGoogleEarthInitGoogleMaps 函数来初始化谷歌地球和谷歌地图。调用 RequestLocationsAjax 加载初始位置,但也可以后续调用 RequestLocationsAjax 函数来动态加载新位置。

然后,将各种处理程序连接到它们各自的控件和按钮。最后,将窗口的间隔设置为 TimerFunction,调用之间的延迟由变量 timerDelay(以毫秒为单位)确定。

$(document).ready(function () {
    SetAutoSyncImage();

    InitGoogleEarth();
    InitGoogleMaps();
    
    RequestLocationsAjax(); // Request initial locations

    $('#locationList').change(function () {
        SyncToLocation();
    });

    $('#syncLocation').click(function () {
        SyncToLocation();
    });

    $('#syncAllLocations').click(function () {
        SyncAllLocations();
    });    

    $('#syncAuto').click(function () {
        autoSync = !autoSync;
        SetAutoSyncImage();
    });

    $('#btnShowEarth').click(function () {
        ShowEarth(this);
    });

    $('#btnShowMap').click(function () {
        ShowMap(this);
    });

    $('#btnShowDirections').click(function () {
        ShowDirections(this);
    });

    $('#btnGetDirections').click(function () {
        GetDirections(this);
    });

    window.setInterval(function () {
        TimerFunction();
    }, timerDelay);
});

标记 (GoogleEarthMaps.cshtml)

所有提供的 jQuery 都是通用的,并且在不同的环境和使用各种浏览器(即 Internet Explorer 11、Firefox 27 和 Chrome 33)时几乎相同。除了稍后确定的一个行之外,下面的标记也是通用的,并且应该在其他环境中正常工作。

由于谷歌地球插件和 Internet Explorer 在仿真模式大于 9 时存在问题(即,插件总是被重新下载),因此在 \Views\Shared\_Layout.cshtml 文件的 head 标签中包含以下行

<meta http-equiv="X-UA-Compatible" content="IE=9">

以下几行包含了谷歌地球和谷歌地图的脚本。对于那些还记得的人来说,请注意,现在 Google 不需要许可证密钥即可使用免费版的谷歌地球或谷歌地图。

<!-- Google Earth Script -->
<script src="http://www.google.com/jsapi"></script>

<!-- Google Maps Script -->
<script src="https://maps.googleapis.com/maps/api/js?libraries=weather&sensor=false"></script>

下面的行不是通用的 HTML,并且包含了我们之前定义的 GoogleEarthMaps.js 脚本。在通用实现中,src 属性的值将被替换为对 GoogleEarthMaps.js 脚本的适用引用。

<!-- Synchronized GoogleEarthMaps Script -->
<script src="@Url.Content("~/Scripts/GoogleEarthMaps.js")"></script>

下表代表一个标题区域,其中包含当前加载位置的选择列表。此外,还有一些按钮可用于同步和显示/隐藏各种视图。

<!-- Header Area -->
<table cellpadding="0" cellspacing="0" style="width:100%">
    <tr>
        <td style="width:45%">
            <table cellpadding="0" cellspacing="0" style="width:100%">
                <tr>
                    <td style="width:50%">
                        <select id="locationList" 
                        class="GEM_Select" style="width:100%" />
                    </td>
                    <td style="width:1%">
                    </td>
                    <td style="width:24%">
                        <input id="syncLocation" class="GEM_Input" 
                        type="button" value="Go To Location" />
                    </td>
                    <td style="width:25%">
                        <input id="syncAllLocations" class="GEM_Input" 
                        type="button" value="View All Locations" />
                    </td>
                </tr>
            </table>
        </td>
        <td style="width:10%; text-align:center">
            <input id="syncAuto" type="image" 
            alt="Auto Sync" width="32" height="32">
        </td>
        <td style="width:45%; text-align:right">
            <input id="btnShowEarth" class="GEM_Input" 
            type="button" value="Hide Earth" />
            <input id="btnShowMap" class="GEM_Input" 
            type="button" value="Hide Map" />
            <input id="btnShowDirections" class="GEM_Input" 
            type="button" value="Show Directions" />
        </td>
    </tr>
</table>

以下外部表有一个左单元格,代表谷歌地球/谷歌地图的组合视图,右单元格代表谷歌地图的驾车路线。驾车路线表格单元格默认不显示,直到请求为止。

左单元格的内部表有一个左单元格,代表谷歌地球视图,右单元格代表谷歌地图视图。这两个表格单元格都可以显示或隐藏。每个左侧和右侧单元格的第一行包含分别用于谷歌地球和谷歌地图的状态行,而每个左侧和右侧单元格的第二行保存了谷歌地球和谷歌地图控件的容器。

下面的标记没有任何 JavaScript,而是使用了分配给适用元素的 ID。当与分离的 GoogleEarthMaps 脚本结合使用时,行为与标记元素相关联。由于标记和 jQuery 的耦合度较低,因此它们各自自然地更易于更改。

<!-- Body Area -->
<table cellpadding="0" cellspacing="0" style="width:100%">
    <tr>
        <td>
            <table cellpadding="0" cellspacing="0" style="width:100%">
                <tr>
                    <td id="tdEarthExt" style="width:50%">
                        <!-- Google Earth status and associated layer controls -->
                        <table cellpadding="0" cellspacing="0" 
                        style="margin-top:5px; width:100%">
                            <tr>
                                <td style="width:50%">
                                    <span id="googleEarthStatus" />
                                </td>
                                <td style="width:50%">
                                    <span style="white-space:nowrap">
                                        <input id="chkGETerrain" 
                                        type="checkbox" checked="checked" />
                                        <label for="chkGETerrain">Terrain</label>
                                    </span>
                                    <span style="white-space:nowrap">
                                        <input id="chkGEBorders" type="checkbox" />
                                        <label for="chkGEBorders">Borders</label>
                                    </span>
                                    <span style="white-space:nowrap">
                                        <input id="chkGERoads" type="checkbox" />
                                        <label for="chkGERoads">Roads</label>
                                    </span>
                                </td>
                            </tr>
                        </table>
                    </td>
                    <td id="tdMapExt" style="width:50%">
                        <!-- Google Maps status and associated layer controls -->
                        <table cellpadding="0" 
                        cellspacing="0" style="margin-top:5px; width:100%">
                            <tr>
                                <td style="width:50%">
                                    <span id="googleMapsStatus" />
                                </td>
                                <td style="width:50%">
                                    <span style="white-space:nowrap">
                                        <input id="chkGMTraffic" type="checkbox" />
                                        <label for="chkGMTraffic">Traffic</label>
                                    </span>
                                    <span style="white-space:nowrap">
                                        <input id="chkGMTransit" type="checkbox" />
                                        <label for="chkGMTransit">Transit</label>
                                    </span>
                                    <span style="white-space:nowrap">
                                        <input id="chkGMBicycle" type="checkbox" />
                                        <label for="chkGMBicycle">Bicycle</label>
                                    </span>
                                    <span style="white-space:nowrap">
                                        <input id="chkGMWeather" type="checkbox" />
                                        <label for="chkGMWeather">Weather</label>
                                    </span>
                                    <span style="white-space:nowrap">
                                        <input id="chkGMClouds" type="checkbox" />
                                        <label for="chkGMClouds">Clouds</label>
                                    </span>
                                </td>
                            </tr>
                        </table>
                    </td>
                </tr>
                <tr>
                    <td id="tdEarth" style="width:50%">
                        <!-- Google Earth container -->
                        <div id="googleEarth" 
                        style="height:8in; border:2px solid transparent" />
                    </td>
                    <td id="tdMap" style="width:50%">
                        <!-- Google Maps container -->
                        <div id="googleMaps" 
                        style="height:8in; border:2px solid transparent" />
                    </td>
                </tr>
            </table>
        </td>
        <td id="tdDirections" style="display:none; width:300px">
            <!-- Google Maps driving directions -->
            <div style="margin-left:6px">
                <div>From: <input type="text" 
                id="fromAddress" value="Escondido, 
                CA" style="width:80%"/></div><br />
                <div><input id="btnGetDirections" class="GEM_Input" 
                type="button" value="Get Directions!" /></div><br />
                <div id="divDirections" 
                style="border-style:solid; border-width:1px; padding:2px"></div>
            </div>
        </td>
    </tr>
</table>

处理位置的异步回发

如前所述,RequestLocationsAjax 函数使用 jQuery 发出异步回发,请求 JSON 格式的位置数组。项目文件 Models/Location.cs 定义了一个单独的位置如下

namespace GoogleEarthMVC.Models
{
    /// <summary>
    /// Represents a location entity.
    /// 
    public class Location
    {
        public double Latitude { get; set; }
        public double Longitude { get; set; }
        public int Altitude { get; set; }
        public string Name { get; set; }
        public string Address { get; set; }
    }
}

项目文件 Controllers/LocationController.cs 以 Json 格式返回了三个位置的示例数组,如下所示

namespace GoogleEarthMVC.Controllers
{
    /// <summary>
    /// Location controller.
    /// 
    public class LocationController : Controller
    {
        // GET: /Location/
        public ActionResult Index()
        {
            return View();
        }

        // GET: /Location/GetLocations
        public JsonResult GetLocations(string region)
        {
            List<location> locList = new List<location>();

            locList.Add(new Location()
            {
                Latitude = 32.715329,
                Longitude = -117.157255,
                Altitude = 10000,
                Name = "San Diego Office",
                Address = "San Diego, CA"
            });

            locList.Add(new Location()
            {
                Latitude = 33.032002840975736,
                Longitude = -117.28510326833907,
                Altitude = 10000,
                Name = "Encinitas Office",
                Address = "Encinitas, CA"
            });

            locList.Add(new Location()
            {
                Latitude = 33.15165880513573,
                Longitude = -117.14846081228425,
                Altitude = 10000,
                Name = "San Marcos Office",
                Address = "San Marcos, CA"
            });

            // Add 3 test locations to the location list and return the JSON
            return Json(locList, JsonRequestBehavior.AllowGet);
        }
    }
}

编译和运行 GoogleEarthMaps

GoogleEarthMap 是一个 ASP.NET MVC 4 Web 应用程序项目,可以使用 Visual Studio 2012 或 Visual Studio 2013 进行构建和运行。但是,需要先还原 NuGet 程序包。幸运的是,借助内置的 NuGet 程序包管理器,只需按一个按钮即可快速轻松地完成此操作。在 Visual Studio 2012 中,启动 NuGet 程序包管理器,如下所示

在 NuGet 程序包管理器中,按 Restore 按钮还原 NuGet 程序包

还原 GoogleEarthMaps 项目使用的 NuGet 程序包后,您应该会看到

您现在可以按 F5 编译并运行 GoogleEarthMaps。首次在浏览器中运行 GoogleEarthMaps 时,您可能需要接受 Google Earth 插件的安装。

关注点

同步的谷歌地球和谷歌地图,以及支持动态重新加载的位置,促进了以一些有趣的方式协同使用这两个功能。希望您会发现该实现大部分是通用的,因此如果需要,可以轻松地将其移植到其他环境。

历史

  • 添加了“处理位置的异步回发”和“编译和运行 GoogleEarthMaps”部分
© . All rights reserved.