Google Earth: The Harrisburg Capitol Complex

 

As I’ve mentioned on here on GEB a few times, I love high-quality 3D models. I think they add a lot to the Google Earth experience, and I enjoy highlighting users that create awesome models.

In the past we’ve shown you work from people like Andy Dell and companies such as Estate3D and CyberCity3D. Today I want to show you some of the work from Steve Cline of Urban 3D Modeling.

 

building.jpg 

He lives in Harrisburg, PA and built out much of the downtown area in 3D. In his words:

“As a resident of Harrisburg I wanted to show off the beautiful architecture and history of our state capitol complex. All the state government buildings are clustered into a dense complex that is bisected by State Street. The principle entrance from the east is the State Street Bridge which passes through two towering pylons as you enter directly into the Capitol Building and surrounding complex. Most of the complex buildings are a mix of neoclassical design and some art deco influence from the later additions. Of the 15 buildings in this collection my personal favorites are the Keystone Building, Judicial Center, and Forum Building. The only building not done by me was the previously done Capitol Building.”

You can find all of the models in Google earth, and he’s also put them together in a collection in the 3D Warehouse. For a quick look at all he’s done, you can use this KMZ tour to fly around and see it all.

For more on Urban 3D Modeling, you can check out their website, follow them on Twitter or connect with them on Facebook.

How to Convert Tile Coordinates

In addition to referencing tiles by quadkey, Bing Maps also refers to tiles by x, y, and z coordinates. This is the way you reference tiles when overriding the GetUri() method of the Silverlight Microsoft.Maps.MapControl.TileSource class, for example, and the x, y, z properties of a tile are passed to the uriConstructor function specified in the options for an AJAX v7 Microsoft.Maps.TileSource.

In the x, y, z tile identification system:

  • z is the zoom level of the grid
  • x and y are the column and row number at which this tile should be placed, measured from an origin at the top left of the map.

Thus, at zoom level 1, you can describe the four tiles required to make a complete Bing Map using x, y, z coordinates as follows:

image

Bing Maps is not entirely unusual in this respect – this exact same referencing system is used by Google Maps, Open Street Maps, ESRI, and many others. You’d therefore be forgiven for thinking that it was some sort of standard.

In fact, this tile numbering sounds a lot like (and is often incorrectly described as) a  TMS index. The Tile Map Service (TMS) Specification defined by the Open Source Geospatial Foundation is a standard for serving map tiles that, like the system described above, places tiles on a grid and refers to their position using x, y, and z coordinates, where z is the zoom level, and x and y refer to column and row positions. However, according to the TMS specifications, “The x-coordinate of the tile numbers increases with the x-coordinate of the spatial reference system, and the y-coordinate of the tile numbers also increases with the y-coordinate of the spatial reference system.”. In other words, tile (0, 0), at any zoom level should always be placed at the bottom left of the map, not the top left.

Using the TMS system, the tile indexes at zoom level 1 become as follows:

image

The problem is that, since these systems are identical in almost every other respect, people tend to assume that the system used by Google/Bing/OSM et al. is the standard and systems that output tiles using it sometimes mistakenly refer to it as the TMS format. Then,  every now and again, you come across a piece of software or service that genuinely outputs tiles numbered according to the TMS standard and, if you don’t correct the tile origin, your tiles all end upside-down on the wrong side of the world :(

Fortunately, it’s a very simple correction to make. At any given map zoom level, zoom, you can invert the y index of a tile from TMS to Google/Bing as follows:

[php]
var ymax = 1 << zoom;
var y = ymax – y – 1;

[/php]

Assuming that you have a set of TMS tiles stored in a subdirectory structure that follows the pattern /z/y/x.png (as described in the Tile Resources section of the OSGeo TMS standard), then here’s a full example showing how to add a tilelayer of TMS tiles to Bing Maps v7:

[php]
<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="http://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=7.0"></script>
<script type="text/javascript">
var map = null;
function GetMap() {
// Create a basic map
map = new Microsoft.Maps.Map(document.getElementById("mapDiv"),
{ credentials: "ENTERYOURBINGMAPSKEY",
center: new Microsoft.Maps.Location(52.6, 1.26),
zoom: 12
});

// Create the tile source
var tileSource = new Microsoft.Maps.TileSource({ uriConstructor: getTMSTilePath });

// Construct the layer using the tile source
var tilelayer = new Microsoft.Maps.TileLayer({ mercator: tileSource, opacity: 1 });

// Push the tile layer to the map
map.entities.push(tilelayer);
}

function getTMSTilePath(tile) {

var x = tile.x;
var z = tile.levelOfDetail;
// Invert tile y origin from top to bottom of map
var ymax = 1 << z;
var y = ymax – tile.y – 1;

return location.href.substring(0, location.href.lastIndexOf(‘/’)) + "/" + z + "/" + x + "/" + y + ".png";
}
</script>
</head>
<body onload="GetMap();">
<div id=’mapDiv’ style="position:relative; width:1024px; height:768px;"></div>
</body>
</html>

[/php]

As a final note, just to add to the confusion, the Web Map Tile Service (WMTS) recently published by the OGC provides an alternative standard to TMS, but, like Google/Bing, uses an origin at the top-left hand corner.

How to Autocomplete Email Addresses in Apps Script

When composing Gmail conversations, the auto-complete feature allows us to see our matching personal contacts as we type and quickly make our contact selections. This time-saving feature can be duplicated when creating Google Apps Script applications. For instance, if you design an application that requires sending emails, you can leverage this auto-complete feature by using a personal contact list.

Defining the Requirements

By observing the behavior while composing Gmail conversations, we can define the requirements of our application.

1. As the user begins typing, a list of matches based on first and last name and email address need to appear under the text box. In other words, the user can begin typing the contacts first name, last name, or their email address.

2. If the desired contact email is listed at the top of the matching list, the user can simply press the Enter key to select it.

3. Another option is to click on any of the contacts in the list.

4. Just in case the user would like to enter an email that is not in their contact list, they may enter the email and press the Enter key.

As an added feature if the email is not formatted correctly, then the invalid email is ignored and not selected. For our application when emails are selected, they will be compiled in a separate list on the right where only the email address is stored. If an email is selected by accident, the user can remove the email by clicking on it.

Designing the Application

The application was designed to mimic the behavior of composing Gmail messages. By doing so, the application avoided the use of buttons, providing an improved user experience.

1. Apps Script Services

The Apps Script’s Spreadsheet Service was used to store a user’s contact data. The Ui Service provided the application interaction with the user, and the Contacts Service was leveraged to gather all the user’s contacts. You may apply a Google Apps domain only filter for the contacts by changing the global variable to “true” in the script.

2. Visualize the Layout

Before writing code, the layout was sketched out to include a text box, some space beneath to list matches, and an area to the right to display the selected emails.

3. Choose your widgets

A text box widget was chosen to allow email entry, and two open list boxes were leveraged to display contact matches and selected emails. List boxes provided the use of click handlers to process email selections.

4. Challenges

To mimic the Gmail auto-complete behavior, the text box needed the ability to handle both keystrokes and a pressed Enter key. To accomplish this, a KeyUpHandler calls a function to identify contact matches via a search. The same function used an e.parameter.keyCode == 13 condition to determine when the enter key is pressed.

[php]<span>//create text box for auto-complete during email lookup in left grid</span>
var textBox = app.createTextBox().setName(<span>’textBox'</span>)
.setWidth(<span>’330px'</span>).setId(<span>’textBox'</span>);  
var tBoxHandler = app.createServerKeyHandler(<span>’search_'</span>);
tBoxHandler.addCallbackElement(textBox); textBox.addKeyUpHandler(tBoxHandler);
…function search_(e){ var app = UiApp.getActiveApplication(); app.getElementById(<span>’list'</span>).clear();
var searchKey = new RegExp(e.parameter.textBox,<span>"gi"</span>); 
<span>if</span> (searchKey == "") app.getElementById(<span>’textBox'</span>).setValue(<span>”</span>);
var range = sheetOwner.getRange(1, 1, sheetOwner.getLastRow(), 2).getValues(); var listBoxCount = 0;
var firstOne = <span>true</span>; <span>for</span> (var i in range){  
// if first/last name available, display name and email address <span>if</span>
(range[i][0].search(searchKey) != -1 || range[i][1].search(searchKey) != -1){ <span>if</span>
(range[i][0].toString()){ app.getElementById(<span>’list'</span>).addItem(range[i][0].toString()+ 
‘ .. ‘+range[i][1].toString(), range[i][1].toString()); 
var listBoxCount = listBoxCount + 1; } <span>else</span> { // else just display the email address 
app.getElementById
(<span>’list'</span>).addItem(range[i][1].toString()); var listBoxCount = listBoxCount + 1; }  
<span>if</span> (firstOne) var firstItem = range[i][1].toString(); var firstOne = <span>false</span>;
} }  
// set the top listbox item as the default <span>if</span> (listBoxCount &gt; 0) app.getElementById(<span>’list’
</span>)
.setItemSelected(0, true);
<span>
// if enter key is pressed in text box, assume they want to add</span> <span>// the email that’s not in the list</span> <span>if</span>
(e.parameter.keyCode==13 &amp;&amp; listBoxCount &lt; 1 &amp;&amp; searchKey !== <span>""</span>) { …[/php]

As this application shows, Apps Script is very powerful. Apps Script has the ability to create applications which allow you to integrate various Google services while building complex user interfaces.

 

You can find Dito’s Email Auto-Complete Script here. To view a video demonstration click here. You can also find Dito Directory on the Google Apps Marketplace.