Click or drag to resize

DataQuery Tutorial

Verizon Connect Logo
Print this page
Learn more about Verizon Connect GeoBase.
Get information about the latest release

The DataQuery object allows you to locate features that are at or near a specific location. It provides a number of static methods that let you obtain information about features of a specific type. These methods fall into two basic categories: queries that locate features near a specified location and queries that locate features within a specific region (bounding box). For an overview of the DataQuery object, see Data Query Concept.

In this tutorial, we will create an application that allows the user to select a type of information to query from a set of list boxes, and to select a location or region on the map using the mouse. The results of the query will then be displayed on the map using BalloonPushPins for each feature located near the specified location or in the specified region.

Prior knowledge

This tutorial assumes you are familiar with setting up a simple GeoBase application. If you have never created a GeoBase application before, you may want to read through the following topics first:

Creating the form
  1. Create a new GeoBase application (including a reference to geobase.net.dll). Name your project Data Query Tutorial.
  2. Add controls to your form so that it looks like the following image:

    Data Query Tutorial Form

  3. Set the names and properties of the controls in your form as indicated in the following table:

    Control (Type)

    Name

    Properties to set

    Form

    Form1

    Set Text to Data Query Tutorial

    ListBox

    lbTarget

    Set Items to the following set of strings:

    Lines

    Polygons

    Points

    POIs

    Streets

    Street Links

    Note Note

    These are the types of features we will be querying. The DataQuery object also has methods to query MultiLines, but that feature requires you to import custom data.

    ListBox

    lbTargetType

    MapCtrl

    mapCtrl

    Set DragBehavior to None.

    Note Note

    In this application, because we will be using mouse clicks to define the point or region for the data query, we are turning off the map control's default mouse handling by setting DragBehavior to None. This means that users can only zoom the map using the mouse wheel and pan the map using edge panning.

Specifying the target types

When we created the form, we populated the lbTarget list box with the types of features we want to query. However, we did not populate the lbTargetType list box, which will contain subtypes for each of these main feature types. Since each main feature type has a different set of subtypes, we populate this list of subtypes by adding a SelectedIndexChanged handler to the lbTarget list box.

Add the following handler for the SelectedIndexChanged event of lbTarget:

C#
private void lbTarget_SelectedIndexChanged(object sender, EventArgs e) {
    lbTargetType.Items.Clear();
    switch (lbTarget.SelectedItem.ToString()) {
        case "Lines":
            LoadLineTypes();
            break;
        case "Polygons":
            LoadPolygonTypes();
            break;
        case "Points":
            LoadPointTypes();
            break;
        case "POIs":
            LoadPOITypes();
            break;
        case "Streets":
            LoadStreetTypes();
            break;
        case "StreetLinks":
            // there are no subtypes for street link queries
            break;
    }
    if (lbTargetType.Items.Count > 0)
        lbTargetType.SelectedIndex = 0;
}

Note that this event handler relies on a number of helper functions (LoadLineTypes, LoadPolygonTypes, LoadPointTypes, and so on.) Add the following code to define these helper functions:

C#
private void LoadLineTypes() {
    lbTargetType.Items.Add("all");
    lbTargetType.Items.Add("railways");
    lbTargetType.Items.Add("rivers");
    lbTargetType.Items.Add("canals");
}
private void LoadPolygonTypes() {
    lbTargetType.Items.Add("all");
    lbTargetType.Items.Add("countries");
    lbTargetType.Items.Add("states");
    lbTargetType.Items.Add("counties");
    lbTargetType.Items.Add("islands");
    lbTargetType.Items.Add("cities");
    lbTargetType.Items.Add("airports");
    lbTargetType.Items.Add("runways");
    lbTargetType.Items.Add("cemeteries");
    lbTargetType.Items.Add("hospitals");
    lbTargetType.Items.Add("industrial");
    lbTargetType.Items.Add("major_parks");
    lbTargetType.Items.Add("state_parks");
    lbTargetType.Items.Add("parks");
    lbTargetType.Items.Add("shopping");
    lbTargetType.Items.Add("sports");
    lbTargetType.Items.Add("universities");
    lbTargetType.Items.Add("golf_courses");
    lbTargetType.Items.Add("native");
    lbTargetType.Items.Add("military");
    lbTargetType.Items.Add("buildings");
    lbTargetType.Items.Add("oceans");
    lbTargetType.Items.Add("bays");
    lbTargetType.Items.Add("water");
}
private void LoadPointTypes() {
    lbTargetType.Items.Add("all");
    lbTargetType.Items.Add("small_villages");
    lbTargetType.Items.Add("villages");
    lbTargetType.Items.Add("large_villages");
    lbTargetType.Items.Add("towns");
    lbTargetType.Items.Add("large_towns");
    lbTargetType.Items.Add("small_cities");
    lbTargetType.Items.Add("cities");
    lbTargetType.Items.Add("major_cities");
}
private void LoadPOITypes() {
    foreach (int i in Enum.GetValues(typeof(PoiType))) {
        lbTargetType.Items.Add(Enum.GetName(typeof(PoiType), i));
    }
}
private void LoadStreetTypes() {
    foreach (int i in Enum.GetValues(typeof(StreetType))) {
        lbTargetType.Items.Add(Enum.GetName(typeof(StreetType), i));
    }
}

For Lines, Polygons, and Points, the sub-types are hard-coded to the names of Predefined query tables. For Points of interest (POIs) and for Streets, we can extract the names from the PoiType and StreetType enumerations, respectively.

Initializing the form

When our application first starts up, we need to add a perform some initialization so that we are ready to display query results on the map. We also want to make sure that the lbTargetType list box is properly populated.

  1. To make the code simpler and more readable, add the following using directive to the top of your source file:
    C#
    using Telogis.GeoBase;
  2. Add the following declarations to your form class:
    C#
    Point mouseDownPoint;
    RendererList renderList = new RendererList();
    const int maxPoints = 25; // maximum number of points to show on map

    The declaration of mouseDownPoint will allow us to capture the mouse position when the user first clicks on the map. The RendererList will allow us to add BalloonPushPin objects to the map so that it displays our query results. Finally, the maxPoints constant will be used to limit the number of results we display on the map, so that it does not get too crowded.

  3. Add the following code to the Form1 constructor, after the call to InitializeComponent():
    C#
    // Initialize the map Control
    mapCtrl.Renderer = renderList;
    mapCtrl.Zoom = ZoomLevel.CityLevel;
    
    // Initialize the list boxes
    lbTarget.SelectedIndex = 0;

    This links our RendererList to the map control, sets the starting zoom level, and selects a default feature type to query. Note that when we select an item in lbTarget, the event handler we added previously causes the lbTargetType list box to be populated with subtypes.

Defining some more helper functions

Before we add event handlers to the map for performing our data queries and displaying them on the map, let's define some helper functions to make that process easier.

  1. Add the following private methods for displaying the results of a query on the map:
    C#
    private bool addPointToMap(string name, LatLon point) {
        BalloonPushPin bb = new BalloonPushPin(name, point);
        bb.ShowInformation = false;
        renderList.Add(bb);
        return renderList.Count > maxPoints;
    }
    private bool addPointToMap(string name, LatLon[] points) {
        foreach (LatLon ll in points) {
            if (mapCtrl.Contains(ll)) {
                return addPointToMap(name, ll);
            }
        }
        // no points on map -- don't bother
        return false;
    }

    These methods generate a BalloonPushPin object and add it to our RendererList so that they are drawn on the map. To simplify the display, we set the push pin's ShowInformation property to false, so that each balloon shows only the name of a feature identified in our data query. After adding the push pin, the method checks whether we have reached the maximum number of features we want to display, and if so, it returns true.

    Note that the second overload takes an array of locations rather than a single location. We will only be displaying a single balloon push pin for each feature our data query identifies. However, many types of features include a number of points. For handling these types, the second overload selects a single location to use from an array of possible locations.

  2. The first parameter of both methods for adding push pins to the map specifies the name that is displayed in the balloon of the push pin. Because this is the only information we are displaying for each feature, we want to make sure that it is as meaningful as possible, even if the Point, Line, Polygon or other feature does not have a defined name (some don't). Add the following methods to extract the most meaningful name possible from each of the data types that we are querying about:
    C#
     private string GetNameForLine(Line line) {
        if (line.Name.Length > 0)
            return line.Name;
        switch (line.Flags) {
            case 0:
                return "railway tunnel";
            case 1:
                return "railway";
            case 2:
                return "river";
            case 3:
                return "canal";
            case 4:
                return "county line";
            case 5: 
                return "state line";
            case 6:
                return "border";
        }
        return "unknown";
    }
    private string GetNameForPolygon(Polygon poly) {
        if (poly.Name.Length > 0)
            return poly.Name;
        switch (poly.Flags) {
            case 0:
                return "ocean";
            case 1:
                return "state";
            case 2:
                return "city";
            case 3:
                return "bay";
            case 4:
                return "water";
            case 5: 
                return "building";
            case 8:
                return "airport";
            case 9:
                return "cemetery";
            case 10:
                return "hospital";
            case 11:
                return "industrial complex";
            case 12:
                return "military base";
            case 13:
                return "national park";
            case 14:
                return "state park";
            case 15:
                return "county park";
            case 16:
                return "shopping center";
            case 17:
                return "Sports complex";
            case 18:
                return "college";
            case 19:
                return "aircraft road";
            case 20:
                return "golf course";
            case 21:
                return "reservation";
            case 22:
                return "country";
            case 23:
                return "county";
            case 24:
                return "island";
        }
        return "unknown";
    }
    private string GetNameForPoint(PointFeature pf) {
        if (pf.Name.Length > 0)
            return pf.Name;
        switch (pf.Flags) {
            case 0:
                return "small village";
            case 1:
                return "village";
            case 2:
                return "large village";
            case 3:
                return "town";
            case 4:
                return "large town";
            case 5:
                return "small city";
            case 6:
                return "city";
            case 7:
                return "major city";
        }
        return "unknown";
    }
    private string GetNameForPoi(Poi poi) {
        if (poi.Name.Length > 0)
            return poi.Name;
        return Enum.GetName(typeof(PoiType), poi.Type);
    }
    private string GetNameForStreet(Street street) {
        foreach (string s in street.Name) {
            if (s.Length > 0)
                return s;
        }
        return "unknown";
    }
    private string GetNameForLink(StreetLink sl) {
        foreach (string s in sl.Names) {
            if (s.Length > 0)
                return s;
        }
        return "type " + sl.Flags.FUNC_CLASS.ToString();
    }

    Each of these methods uses the name of the feature if it is available. If it is not available, a name is constructed based on the other information in the object.

Querying for features

We are now ready to use the DataQuery object to query our map data about the different feature types.

When the user clicks down on the mouse button, we will capture the current location of the mouse.

When the user releases the mouse button, we will check the location of the mouse again. If it is the same as when the mouse went down, (a simple click), we will use the DataQuery to locate the closest features of the target type that are near the point where the user clicked. If the mouse was released in a different location (a click, drag, and release), we will use the DataQuery to locate all features of the target type that lie within the region defined by the user's click and drag action.

Finally, we will use the helper functions we have already defined to draw BalloonPushPin objects on the map, displaying the results of our query.

  1. Add the following MouseDown event handler to the map control:
    C#
    private void mapCtrl_MouseDown(object sender, MouseEventArgs e) {
        mouseDownPoint = e.Location;
    }

    This captures and saves the location of the mouse when the user clicked the mouse button.

  2. Add the following MouseUp event handler to the map control:
    C#
    private void mapCtrl_MouseUp(object sender, MouseEventArgs e) {
                BoundingBox bb;
    
                renderList.Clear(); // we are replacing any push pins added previously
    
                switch (lbTarget.SelectedItem.ToString()) {
                    case "Lines":
                        Line[] lineResults;
                        // if mouse goes up where it went down, this means find nearest to point
                        if (e.Location == mouseDownPoint) {
                            LatLon location = mapCtrl.XYtoLatLon(e.X, e.Y);
                            lineResults = DataQuery.FindNearestLines(location, lbTargetType.SelectedItem.ToString());
                        } else {
                            // if mouse moved before going up, this means find lines inside the defined region
                            bb = new BoundingBox(
                                mapCtrl.XYtoLatLon(mouseDownPoint.X, mouseDownPoint.Y),
                                mapCtrl.XYtoLatLon(e.X, e.Y));
                            lineResults = DataQuery.QueryLines(bb, lbTargetType.SelectedItem.ToString());
                        }
                        // add push pins for the first point in each line of the results
                        foreach (Line l in lineResults) {
                            if (addPointToMap(GetNameForLine(l), l.Points))
                                break;
                        }
                        break;
                     case "Polygons":
                        Polygon[] polygonResults;
                        // if mouse goes up where it went down, this means find nearest to point
                        if (e.Location == mouseDownPoint) {
                            LatLon location = mapCtrl.XYtoLatLon(e.X, e.Y);
                            polygonResults = DataQuery.FindNearestPolygons(location, lbTargetType.SelectedItem.ToString());
                        } else {
                            // if mouse moved before going up, this means find polygons inside the defined region
                            bb = new BoundingBox(
                                mapCtrl.XYtoLatLon(mouseDownPoint.X, mouseDownPoint.Y),
                                mapCtrl.XYtoLatLon(e.X, e.Y));
                            polygonResults = DataQuery.QueryPolygons(bb, lbTargetType.SelectedItem.ToString());
                        }
                        // add push pins for the first point in the outer ring of each polygon of the results
                        foreach (Polygon p in polygonResults) {
                            if (addPointToMap(GetNameForPolygon(p), p.Geometry.OuterRing.Points.Points))
                                break;
                        }
                        break;
                    case "Points":
                        PointFeature[] pointResults;
                        // if mouse goes up where it went down, this means find nearest to point
                        if (e.Location == mouseDownPoint) {
                            LatLon location = mapCtrl.XYtoLatLon(e.X, e.Y);
                            pointResults = DataQuery.FindNearestPoints(location, lbTargetType.SelectedItem.ToString());
                        } else {
                            // if mouse moved before going up, this means find points inside the defined region
                            bb = new BoundingBox(
                                mapCtrl.XYtoLatLon(mouseDownPoint.X, mouseDownPoint.Y),
                                mapCtrl.XYtoLatLon(e.X, e.Y));
                            pointResults = DataQuery.QueryPoints(bb, lbTargetType.SelectedItem.ToString());
                        }
                        // add push pins for each point feature of the results
                        foreach (PointFeature p in pointResults) {
                            if (addPointToMap(GetNameForPoint(p), p.Point))
                                break;
                        }
                        break;
                    case "POIs":
                        // POIs can only be located within a region
                        bb = new BoundingBox(
                            mapCtrl.XYtoLatLon(mouseDownPoint.X, mouseDownPoint.Y),
                            mapCtrl.XYtoLatLon(e.X, e.Y));
                        if (e.Location == mouseDownPoint) {
                            // if we only have a single point, inflate the bounding box
                            bb.Inflate(0.5);
                        }
                        // we are filtering POIs by type but not name
                        Poi[] poiResults = DataQuery.QueryPoi(bb, 
                            new PoiType[] {(PoiType) Enum.Parse(typeof(PoiType),lbTargetType.SelectedItem.ToString())}, 
                            null);
                        // add push pins with the name and location of each poi
                        foreach (Poi poi in poiResults) {
                            if (addPointToMap(GetNameForPoi(poi), poi.Location))
                                break;
                        }
                        break;
                    case "Streets":
                        // Streets can only be located within a region
                        bb = new BoundingBox(
                            mapCtrl.XYtoLatLon(mouseDownPoint.X, mouseDownPoint.Y),
                            mapCtrl.XYtoLatLon(e.X, e.Y));
                        if (e.Location == mouseDownPoint) {
                            // if we only have a single point, inflate the bounding box
                            bb.Inflate(0.5);
                        }
                        Street[] streetResults = DataQuery.QueryStreets(bb,
                             (StreetType)Enum.Parse(typeof(StreetType), lbTargetType.SelectedItem.ToString()));
                        // add push pins with the first name of each street and the first point on the street
                        foreach (Street s in streetResults) {
                            if (addPointToMap(GetNameForStreet(s), s.Points))
                                break;
                        }
                        break;
                    case "Street Links":
                        // Street Links can only be located within a region
                        bb = new BoundingBox(
                            mapCtrl.XYtoLatLon(mouseDownPoint.X, mouseDownPoint.Y),
                            mapCtrl.XYtoLatLon(e.X, e.Y));
                        if (e.Location == mouseDownPoint) {
                            // if we only have a single point, inflate the bounding box
                            bb.Inflate(0.5);
                        }
                        StreetLink[] slResults = DataQuery.QueryLinks(bb);
                        // add push pins with the first name of each street link and the first point on the street link
                        foreach (StreetLink sl in slResults) {
                            if (addPointToMap(GetNameForLink(sl), sl.Points))
                                break;
                        }
                        break;
    
                }
                mapCtrl.Invalidate();
                mapCtrl.Update();
            }

    This clears the RendererList of any push pins from the last query. It then checks the point where the mouse came up and either calls the appropriate FindNearest... method or constructs a bounding box for the implied region and calls the appropriate Query... method. The results of the data query are then added to the map using one of our addPointToMap helper functions. Finally, we invalidate the map control so that it repaints with the new query results.

Testing the application

Run your application. When the form first appears, it should look like the following screen shot:

Data Query Application

Try selecting different types of features and then clicking on the map or clicking and dragging on the map. The map updates to show the features nearest where you clicked or within the region you dragged out:

Data Query Results