Exemple #1
0
        protected override void InternalGET(System.Web.HttpContext context, HandlerTimedCache cache)
        {
            IList <SecurityRole> roles = UserHelper.GetUserRoles(context.User.Identity.Name);
            //Get the paging parameters...
            int page     = WebUtil.ParseIntParam(context, "page");
            int pageSize = WebUtil.ParseIntParam(context, "pageSize");

            // Check to see if this is a csv export request.  Runs the normal query (with no paging).
            bool csv = false;

            WebUtil.ParseOptionalBoolParam(context, "csv", ref csv);

            // If this is csv, we want all data - override any paging
            if (csv)
            {
                page     = -1;
                pageSize = -1;
            }

            // Now get the ordering parameters, if specified.
            int sortCol = -1;

            WebUtil.ParseOptionalIntParam(context, "sortBy", ref sortCol);
            SortType?sortDir = null;

            if (sortCol >= 0)
            {
                // Default is ascending sort, passing false means descending.
                bool ascending = true;
                WebUtil.ParseOptionalBoolParam(context, "sortasc", ref ascending);
                sortDir = ascending ? SortType.Asc : SortType.Desc;
            }

            string            indicatorId = WebUtil.GetParam(context, "indicator", false);
            NycResolutionType resolution  = WebUtil.ParseEnumParam <NycResolutionType>(context, "resolution");
            NycTimeframeType  timetype    = WebUtil.ParseEnumParam <NycTimeframeType>(context, "timetype");
            int minyear = WebUtil.ParseIntParam(context, "minyear");
            int maxyear = WebUtil.ParseIntParam(context, "maxyear");

            // These two params are for "scope".  These should be "ActualId" not "UID".
            string borough    = WebUtil.GetParam(context, "borough", true);
            string subborough = WebUtil.GetParam(context, "subborough", true);

            NycResultsWithMetadata list = NychanisHelper.Query(indicatorId, resolution, timetype, minyear, maxyear, borough, subborough, sortCol, sortDir, pageSize, page);

            // If this was a csv request, format it and return it instead
            if (csv)
            {
                // Generate actual csv data, determine if this is groupby'd or not
                string export = NychanisHelper.ResultsAsCsv(list, indicatorId);

                // Setup the response to handle this type of request
                context.Response.AddHeader("Content-Disposition", "attachment;filename=Furman_Center_Neighborhood_Info.csv");
                context.Response.ContentType = "text/csv";
                context.Response.Write(export);
                return;
            }
            // Return the results to the client
            context.Response.Write(WebUtil.ObjectToJson(list));
        }
Exemple #2
0
 /// <summary>
 /// Extract its info from a geography.
 /// </summary>
 /// <param name="geog"></param>
 public NycResolution(NycGeography geog)
 {
     Name              = geog.ResolutionName;
     UID               = geog.Resolution;
     Order             = geog.ResolutionOrder;
     HasBoroughData    = StringHelper.IsNonBlank(geog.Borough);
     HasSubBoroughData = StringHelper.IsNonBlank(geog.SubBorough);
     Geographies.Add(new ThinNycGeography(geog));
 }
Exemple #3
0
        protected override void InternalGET(HttpContext context, HandlerTimedCache cache)
        {
            string            indicatorId = WebUtil.GetParam(context, "indicator", false);
            NycResolutionType resolution  = WebUtil.ParseEnumParam <NycResolutionType>(context, "resolution");
            string            timeId      = WebUtil.GetParam(context, "time", false);

            // These two params are for "scope".  These should be "ActualId" not "UID".
            string borough    = WebUtil.GetParam(context, "borough", true);
            string subborough = WebUtil.GetParam(context, "subborough", true);

            string sld = NychanisHelper.GenerateSld(indicatorId, resolution, timeId, borough, subborough);

            context.Response.ContentType = "text/xml";
            context.Response.Write(sld);
        }
Exemple #4
0
        /// <summary>
        /// Runs a query for the data for a particular indicator/resolution/time,
        /// and generates an SLD to color the geographies correctly based on the values.
        /// </summary>
        /// <param name="indicatorId"></param>
        /// <param name="resolutionType"></param>
        /// <param name="timeId"></param>
        /// <param name="scopeBorough"></param>
        /// <param name="scopeSubborough"></param>
        /// <returns></returns>
        public static string GenerateSld(object indicatorId, NycResolutionType resolutionType,
                                         object timeId, object scopeBorough, object scopeSubborough)
        {
            // First load the indicator metadata, both because we need the info, and because
            // if the query is for data that doesn't exist, we can skip a lot of work.
            NycIndicator indicator = GetIndicator(indicatorId);
            // Don't validate time, since there's no easy way to do it other than querying and seeing
            // if we get anything.

            // Now load the configs that tell us what color to render everything as.
            Config cfg = Config.GetConfig("NYC.SLD");

            // Acceptable values are 0 to 100.  So we need a 101 length array.
            string[] colors = new string[101];
            string   color  = null;
            IDictionary <string, IList <object> > geogIdsByColor = new CheckedDictionary <string, IList <object> >();

            for (int x = 0; x < colors.Length; x++)
            {
                // If the value isn't in the config, use the same as the last value.
                color = cfg.GetParameter(indicator.UseAlternateColors ? "AlternateBreakpoints" : "BreakPoints",
                                         x.ToString(), color);
                // Make sure we have a list created for the geogs that will be this color.
                if ((color != null) && (!geogIdsByColor.ContainsKey(color)))
                {
                    geogIdsByColor[color] = new List <object>();
                }
                colors[x] = color;
            }

            string mapLayerName          = cfg.GetParameter("LayerNames", resolutionType.ToString(), null);
            string layerGeogIdField      = cfg.GetParameter("LayerGeographyIdFields", resolutionType.ToString(), null);
            string layerDisplayNameField = cfg.GetParameter("LayerDisplayNameFields", resolutionType.ToString(), null);

            // This includes any labeling and outlines, default background color, etc.
            StringBuilder sldRules = new StringBuilder(cfg.GetConfigInnerXml("DefaultSLD"));

            // Replace any tokens with the appropriate values for this layer.
            sldRules.Replace("{LayerName}", mapLayerName);
            sldRules.Replace("{LayerGeographyIdField}", layerGeogIdField);
            sldRules.Replace("{LayerDisplayNameField}", layerDisplayNameField);

            // If no map layer is defined (I.E. "City"), or the request is invalid in some way,
            // use just the default SLD.
            if (mapLayerName != null)
            {
                // Now query for all the data.
                DaoCriteria crit = new DaoCriteria();
                crit.Expressions.Add(new EqualExpression("IndicatorId", indicatorId));
                crit.Expressions.Add(new EqualExpression("Resolution", resolutionType));
                crit.Expressions.Add(new EqualExpression("TimeId", timeId));
                DaoCriteria geogCrit = null;
                if (scopeBorough != null)
                {
                    geogCrit = new DaoCriteria();
                    geogCrit.Expressions.Add(new EqualExpression("Borough", scopeBorough));
                    if (scopeSubborough != null)
                    {
                        geogCrit.Expressions.Add(new EqualExpression("SubBorough", scopeSubborough));
                    }
                }
                if (geogCrit != null)
                {
                    // Scope and/or subscope was defined.
                    geogCrit.Expressions.Add(new EqualExpression("Resolution", resolutionType));
                    IList <object> geogIdsInScope = new List <object>();
                    foreach (NycGeography geog in _geogDao.Get(geogCrit))
                    {
                        geogIdsInScope.Add(geog.UID);
                    }
                    crit.Expressions.Add(new PropertyInListExpression("GeographyId", geogIdsInScope));
                }
                IList <NycDatum> data = _dataDao.Get(crit);

                // If there's no data, don't make any SLD rules.
                if ((data != null) && (data.Count > 0))
                {
                    foreach (NycDatum datum in data)
                    {
                        int val;
                        switch (indicator.Breakpoint)
                        {
                        case NycBreakpointType.HistoricalBreakpoint:
                            val = datum.HistoricalBreakpoint;
                            break;

                        case NycBreakpointType.ContemporaryBreakpoint:
                            val = datum.ContemporaryBreakpoint;
                            break;

                        default:
                            throw new ArgumentOutOfRangeException("Indicator " + indicator +
                                                                  " has invalid breakpoint: " + indicator.Breakpoint);
                        }
                        if ((val < 0) || (val > 100))
                        {
                            _log.Warn("'Normalized' value " + datum + " was outside 0-100 range.");
                        }

                        // Figure out, based on the value, which color group it belongs to.
                        string thisColor = colors[val];
                        if (thisColor != null)
                        {
                            // Put it in that group.
                            geogIdsByColor[thisColor].Add(datum.GeographyId);
                        }
                    }

                    // Now append the rules for those color groups.
                    foreach (KeyValuePair <string, IList <object> > kvp in geogIdsByColor)
                    {
                        // Only add rules for colors that actually have values.
                        if (kvp.Value.Count > 0)
                        {
                            sldRules.Append("<Rule>");

                            // The part that says what geography IDs get this color.
                            sldRules.Append("<ogc:Filter>");
                            sldRules.Append("<ogc:Or>");
                            foreach (object geogId in kvp.Value)
                            {
                                sldRules.Append("<ogc:PropertyIsEqualTo>");
                                sldRules.Append("<ogc:PropertyName>").Append(layerGeogIdField).Append(
                                    "</ogc:PropertyName>");
                                sldRules.Append("<ogc:Literal>").Append(geogId).Append("</ogc:Literal>");
                                sldRules.Append("</ogc:PropertyIsEqualTo>");
                            }
                            sldRules.Append("</ogc:Or>");
                            sldRules.Append("</ogc:Filter>");

                            // The part that says what color to fill with.
                            sldRules.Append("<PolygonSymbolizer>");
                            sldRules.Append("<Fill>");
                            sldRules.Append("<CssParameter name=\"fill\">").Append(kvp.Key).Append("</CssParameter>");
                            sldRules.Append("<CssParameter name=\"fill-opacity\">").Append(
                                cfg.GetParameter("Polygons", "Opacity")).Append("</CssParameter>");
                            sldRules.Append("</Fill>");
                            sldRules.Append("</PolygonSymbolizer>");

                            sldRules.Append("</Rule>");
                        }
                    }
                }
            }
            // Now wrap all the rendering rules in the SLD outer tags.
            StringBuilder sld = new StringBuilder();

            sld.Append("<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n")
            .Append("<StyledLayerDescriptor version=\"1.0.0\"")
            .Append(" xsi:schemaLocation=\"http://www.opengis.net/sld StyledLayerDescriptor.xsd\"")
            .Append(" xmlns=\"http://www.opengis.net/sld\"")
            .Append(" xmlns:ogc=\"http://www.opengis.net/ogc\"")
            .Append(" xmlns:xlink=\"http://www.w3.org/1999/xlink\"")
            .Append(" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">")
            .Append("<NamedLayer>")
            .Append("<Name>").Append(mapLayerName).Append("</Name>")
            .Append("<UserStyle>")
            .Append("<FeatureTypeStyle>")
            .Append(sldRules.ToString())
            .Append("</FeatureTypeStyle>")
            .Append("</UserStyle>")
            .Append("</NamedLayer>")
            .Append("</StyledLayerDescriptor>");

            return(sld.ToString());
        }
Exemple #5
0
        /// <summary>
        /// Generates a list of label/color pairs with which one can create a legend
        /// </summary>
        /// <returns></returns>
        public static NycLegendInfo GenerateLegendList(NycIndicator indicator, NycResolutionType resolution)
        {
            NycLegendInfo retVal = new NycLegendInfo();
            // Get the color and breakpoint information from the config file
            Config cfg = Config.GetConfig("NYC.SLD");

            retVal.Opacity   = cfg.GetParameter("Polygons", "Opacity");
            retVal.ValueType = (indicator.ValueType == null) ? null : indicator.ValueType.ToString();

            IList <KeyValuePair <string, string> > paramList = cfg.GetParametersAsList(indicator.UseAlternateColors ? "AlternateBreakpoints" : "BreakPoints");

            retVal.Elements = new List <NycLegendElement>();

            // Loop through each named breakpoint and get a list of associated colors
            float absoluteMinVal;
            float absoluteMaxVal;

            switch (indicator.Breakpoint)
            {
            case NycBreakpointType.HistoricalBreakpoint:
                switch (resolution)
                {
                case NycResolutionType.Borough:
                    absoluteMinVal = indicator.HistoricBoroughMin;
                    absoluteMaxVal = indicator.HistoricBoroughMax;
                    break;

                case NycResolutionType.CensusTract:
                    absoluteMinVal = indicator.HistoricCensusTractMin;
                    absoluteMaxVal = indicator.HistoricCensusTractMax;
                    break;

                case NycResolutionType.City:
                    absoluteMinVal = indicator.HistoricCityMin;
                    absoluteMaxVal = indicator.HistoricCityMax;
                    break;

                case NycResolutionType.CommunityDistrict:
                    absoluteMinVal = indicator.HistoricCommunityDistrictMin;
                    absoluteMaxVal = indicator.HistoricCommunityDistrictMax;
                    break;

                case NycResolutionType.PolicePrecinct:
                    absoluteMinVal = indicator.HistoricPolicePrecinctMin;
                    absoluteMaxVal = indicator.HistoricPolicePrecinctMax;
                    break;

                case NycResolutionType.SchoolDistrict:
                    absoluteMinVal = indicator.HistoricSchoolDistrictMin;
                    absoluteMaxVal = indicator.HistoricSchoolDistrictMax;
                    break;

                case NycResolutionType.SubBorough:
                    absoluteMinVal = indicator.HistoricSubBoroughMin;
                    absoluteMaxVal = indicator.HistoricSubBoroughMax;
                    break;

                default:
                    throw new NotSupportedException("Resolution type " + resolution + " isn't supported yet.");
                }
                break;

            case NycBreakpointType.ContemporaryBreakpoint:
                switch (resolution)
                {
                case NycResolutionType.Borough:
                    absoluteMinVal = indicator.ContemporaryBoroughMin;
                    absoluteMaxVal = indicator.ContemporaryBoroughMax;
                    break;

                case NycResolutionType.CensusTract:
                    absoluteMinVal = indicator.ContemporaryCensusTractMin;
                    absoluteMaxVal = indicator.ContemporaryCensusTractMax;
                    break;

                case NycResolutionType.City:
                    absoluteMinVal = indicator.ContemporaryCityMin;
                    absoluteMaxVal = indicator.ContemporaryCityMax;
                    break;

                case NycResolutionType.CommunityDistrict:
                    absoluteMinVal = indicator.ContemporaryCommunityDistrictMin;
                    absoluteMaxVal = indicator.ContemporaryCommunityDistrictMax;
                    break;

                case NycResolutionType.PolicePrecinct:
                    absoluteMinVal = indicator.ContemporaryPolicePrecinctMin;
                    absoluteMaxVal = indicator.ContemporaryPolicePrecinctMax;
                    break;

                case NycResolutionType.SchoolDistrict:
                    absoluteMinVal = indicator.ContemporarySchoolDistrictMin;
                    absoluteMaxVal = indicator.ContemporarySchoolDistrictMax;
                    break;

                case NycResolutionType.SubBorough:
                    absoluteMinVal = indicator.ContemporarySubBoroughMin;
                    absoluteMaxVal = indicator.ContemporarySubBoroughMax;
                    break;

                default:
                    throw new NotSupportedException("Resolution type " + resolution + " isn't supported yet.");
                }
                break;

            default:
                throw new NotSupportedException("Breakpoint type " + indicator.Breakpoint + " isn't supported yet.");
            }
            float totalRange = absoluteMaxVal - absoluteMinVal;

            for (int i = 0; i < paramList.Count; i++)
            {
                NycLegendElement element = new NycLegendElement();
                element.Color = paramList[i].Value;
                // Always start at the absolute min val.
                element.MinValue = absoluteMinVal;
                // If this is after the first one, add the fraction of the range.
                if (i > 0)
                {
                    element.MinValue += ((float.Parse(paramList[i].Key) / 100.0f) * totalRange);
                }

                if (i + 1 < paramList.Count)
                {
                    // This isn't the last element, so use the next one to get the range.
                    element.MaxValue = ((float.Parse(paramList[i + 1].Key) / 100.0f) * totalRange) + absoluteMinVal;
                }
                else
                {
                    // Last element, so always use the absolute max value.
                    element.MaxValue = absoluteMaxVal;
                }

                // Add our range and color
                retVal.Elements.Add(element);
            }

            return(retVal);
        }
Exemple #6
0
 /// <summary>
 /// Queries for Nychanis data for the specified year range, resolution, and indicator.
 /// </summary>
 /// <param name="indicatorId">ID of the indicator being queried.</param>
 /// <param name="resolutionType">What resolution are we querying for.</param>
 /// <param name="timeUnitType">What type of time units should the data be in?</param>
 /// <param name="startYear">First year to include in the results.</param>
 /// <param name="endYear">Last year to include in the results.</param>
 public static NycResultsWithMetadata Query(object indicatorId,
                                            NycResolutionType resolutionType, NycTimeframeType timeUnitType, int startYear, int endYear)
 {
     // Overload with default ordering
     return(Query(indicatorId, resolutionType, timeUnitType, startYear, endYear, null, null, 0, SortType.Asc, -1, -1));
 }
Exemple #7
0
        private static NycTableResults QueryNychanisData(object indicatorId,
                                                         NycResolutionType resolutionType,
                                                         object scopeBorough, object scopeSubborough, IDictionary <object, int> colsByTimeId)
        {
            // Query both the geography and data tables in a join so we can get the
            // nice display name of the geography rather than just the ID.  NOTE: This could
            // be done by obtaining the geographies in a single query then the data in a second
            // query.  At the moment this seems better.
            DaoJoinCriteria joinCrit = new DaoJoinCriteria();

            joinCrit.Expressions.Add(new EqualJoinExpression("GeographyId", "UID"));
            // Add filters to the data (left) side of the join.
            DaoCriteria dataCrit = new DaoCriteria();

            joinCrit.LeftCriteria = dataCrit;
            // Only the indicator that was requested.
            dataCrit.Expressions.Add(new EqualExpression("IndicatorId", indicatorId));
            // Only the resolution that was requested.
            dataCrit.Expressions.Add(new EqualExpression("Resolution", resolutionType));
            // Only the time span that was requested.
            dataCrit.Expressions.Add(new PropertyInListExpression("TimeId", colsByTimeId.Keys));

            // Add support for querying for a single borough or subborough, "scope"
            DaoCriteria scopeCrit = new DaoCriteria();

            if (scopeBorough != null)
            {
                scopeCrit.Expressions.Add(new EqualExpression("Borough", scopeBorough));
                if (scopeSubborough != null)
                {
                    scopeCrit.Expressions.Add(new EqualExpression("SubBorough", scopeSubborough));
                }
            }

            // If we added any expressions, add the criteria to the geography side of the join
            if (scopeCrit.Expressions.Count > 0)
            {
                joinCrit.RightCriteria = scopeCrit;
            }

            // Sort the results by geography then by time (descending).  This allows us to populate
            // the results structure easily, even if we later resort based on the user's choice.
            // We sort first by geography name, which "should" be unique, but just in case we also
            // sort by geography ID.
            joinCrit.Orders.Add(new JoinSortOrder("Name", false));
            joinCrit.Orders.Add(new JoinSortOrder("GeographyId", true));
            joinCrit.Orders.Add(new JoinSortOrder("TimeId", SortType.Desc, true));

            IList <JoinResult <NycDatum, NycGeography> > dataAndGeogs = _dataDao.Get(joinCrit, _geogDao);

            NycTableResults        results = new NycTableResults();
            List <IList <object> > values  = new List <IList <object> >();

            // Now, for each geography ID, add all its year values to a list and add to the values collection.
            if (dataAndGeogs.Count > 0)
            {
                object         lastGeogId          = null;
                IList <object> resultArray         = null;
                IList <bool>   dataAvailableByTime = new bool[colsByTimeId.Count];

                foreach (JoinResult <NycDatum, NycGeography> result in dataAndGeogs)
                {
                    if ((lastGeogId == null) || (!lastGeogId.Equals(result.Left.GeographyId)))
                    {
                        // Create a new array, at the correct length already in case there
                        // are holes in the data.
                        resultArray    = new object[colsByTimeId.Count + 1];
                        resultArray[0] = result.Right.Name;
                        lastGeogId     = result.Left.GeographyId;
                        // Add it to the list to be returned.
                        values.Add(resultArray);
                    }
                    // Always populate this value in the array.
                    resultArray[colsByTimeId[result.Left.TimeId]]             = result.Left.Value;
                    dataAvailableByTime[colsByTimeId[result.Left.TimeId] - 1] = true;
                }

                results.DataAvailableByTime = dataAvailableByTime;
            }
            results.Values = values;

            return(results);
        }
Exemple #8
0
        /// <summary>
        /// Queries for Nychanis data for the specified year range, resolution, and indicator.
        /// </summary>
        /// <param name="indicatorId">ID of the indicator being queried.</param>
        /// <param name="resolutionType">What resolution are we querying for.</param>
        /// <param name="timeUnitType">What type of time units should the data be in?</param>
        /// <param name="startYear">First year to include in the results.</param>
        /// <param name="endYear">Last year to include in the results.</param>
        /// <param name="scopeSubborough"></param>
        /// <param name="scopeBorough"></param>
        /// <param name="orderCol">The column to sort by.</param>
        /// <param name="orderDir">The direction to sort, ignored if order is less than zero.
        /// <param name="numPerPage">Number of records to be returned in the page sequence, if not less than zero</param>
        /// <param name="page">Which page in this page sequence is this request for.
        ///                        If null, assumed to be ascending.</param>
        /// <returns>The results!</returns>
        public static NycResultsWithMetadata Query(object indicatorId,
                                                   NycResolutionType resolutionType, NycTimeframeType timeUnitType, int startYear, int endYear,
                                                   object scopeBorough, object scopeSubborough,
                                                   int orderCol, SortType?orderDir, int numPerPage, int page)
        {
            NycResultsWithMetadata retVal = new NycResultsWithMetadata();
            // First load the indicator metadata, both because we need the display name, and because
            // if the query is for data that doesn't exist, we can skip a lot of work.
            NycIndicator indicator = GetIndicator(indicatorId);

            retVal.Indicator  = indicator.Name;
            retVal.Resolution = resolutionType.ToString();
            // Now verify the query against the metadata.
            retVal.MinYear = (startYear < indicator.MinYear) ? indicator.MinYear : startYear;
            retVal.MaxYear = (endYear > indicator.MaxYear) ? indicator.MaxYear : endYear;
            bool validRequest = (retVal.MaxYear >= retVal.MinYear);

            if (!validRequest)
            {
                // Return a completely blank result object.
                return(retVal);
            }

            // We only want to load time metadata for times this indicator actually has data for.
            DaoJoinCriteria joinCrit = new DaoJoinCriteria();

            joinCrit.RightCriteria = new DaoCriteria();
            joinCrit.RightCriteria.Expressions.Add(new EqualExpression("IndicatorId", indicatorId));
            joinCrit.RightCriteria.Expressions.Add(new EqualExpression("Resolution", resolutionType));
            joinCrit.Expressions.Add(new EqualJoinExpression("UID", "TimeId"));
            // Load the time metadata.
            joinCrit.LeftCriteria = new DaoCriteria();
            // These are not-ed so they are <= and >=;
            joinCrit.LeftCriteria.Expressions.Add(new GreaterExpression("Year", retVal.MaxYear, false));
            joinCrit.LeftCriteria.Expressions.Add(new LesserExpression("Year", retVal.MinYear, false));
            joinCrit.LeftCriteria.Expressions.Add(new EqualExpression("Type", timeUnitType));
            // Sort by value descending, so the most recent year comes first.
            joinCrit.Orders.Add(new JoinSortOrder("Value", SortType.Asc, true));
            List <JoinResult <NycTimeframe, NycResolutionForIndicator> > timeframes = _timeDao.Get(joinCrit, _resolutionDao);

            // We also need to know what all possible times are though, so we can render the fancy slider with appropriate gaps.
            joinCrit.LeftCriteria.Orders.Add(new SortOrder("Value", SortType.Asc));
            IList <NycTimeframe> allTimeframes = _timeDao.Get(joinCrit.LeftCriteria);

            // Use them to assemble the metadata, since one year is one column.
            retVal.Attrs = new List <AbstractNamedSortable>();
            retVal.Attrs.Add(new NycGeogColumnMetadata("Area"));
            IDictionary <object, int> colsByTimeId = new CheckedDictionary <object, int>();

            foreach (JoinResult <NycTimeframe, NycResolutionForIndicator> timeframe in timeframes)
            {
                colsByTimeId[timeframe.Left.UID] = retVal.Attrs.Count;
                retVal.Attrs.Add(new NycYearColumnMetadata(timeframe.Left, indicator.ValueType));
            }

            NycTableResults results = QueryNychanisData(indicatorId, resolutionType,
                                                        scopeBorough, scopeSubborough, colsByTimeId);

            retVal.TotalResults = results.Values.Count;

            // Don't do any further processing if there are no results
            if (results.Values.Count == 0)
            {
                return(retVal);
            }

            // If the user specified a sort order, use that, otherwise sort by the first column (area).
            List <KeyValuePair <int, SortType> > colSorts = new List <KeyValuePair <int, SortType> >();

            colSorts.Add(new KeyValuePair <int, SortType>(orderCol < 0 ? 0 : orderCol, orderDir ?? SortType.Asc));
            results.Values.Sort(new MultipleColumnListComparer(colSorts));

            // Truncate the results by the requested paging information
            retVal.Values = ResultsWithMetadata <AbstractNamedSortable> .GetPagedSubset(results.Values, numPerPage, page);

            // Now get context rows.  Always get City, unless we're querying for City.
            if (resolutionType != NycResolutionType.City)
            {
                List <IList <object> > contextRows = QueryNychanisData(indicatorId, NycResolutionType.City,
                                                                       null, null, colsByTimeId).Values;
                // Now, if they provided a borough scope and didn't ask for a borough resolution,
                // include borough.
                if ((resolutionType != NycResolutionType.Borough) && (scopeBorough != null))
                {
                    contextRows.AddRange(QueryNychanisData(indicatorId, NycResolutionType.Borough,
                                                           scopeBorough, null, colsByTimeId).Values);
                    // Then if not subborough but a subborough is provided, include that.
                    if ((resolutionType != NycResolutionType.SubBorough) && (scopeSubborough != null))
                    {
                        contextRows.AddRange(QueryNychanisData(indicatorId, NycResolutionType.SubBorough,
                                                               scopeBorough, scopeSubborough, colsByTimeId).Values);
                    }
                }
                retVal.ContextRows = contextRows;
            }
            // Now generate the map info for showing the results as a cloropleth layer.
            Config cfg           = Config.GetConfig("PDP.Data");
            string sldHandlerUrl = cfg.GetParameter("Mapping", "SldHandlerURL");

            retVal.MapInfo        = new NycMapInfo();
            retVal.MapInfo.Server = cfg.GetParameter("Mapping", "MapServerURL");
            retVal.MapInfo.Layers = new List <NycLayerInfo>();
            string layerName = Config.GetConfig("NYC.SLD").GetParameter("LayerNames", resolutionType.ToString(), null);

            // City doesn't have a map layer, so don't create any OL layers for it.
            if (layerName != null)
            {
                int possibleTimeIndex = 0;
                int actualTimeIndex   = 0;
                while (possibleTimeIndex < allTimeframes.Count)
                {
                    // We need to pad out the list of layers with blanks for timeframes that lack data.
                    NycLayerInfo layer = new NycLayerInfo();
                    layer.Name = allTimeframes[possibleTimeIndex].Name;
                    // Years that actually have data go up faster than all years, so check if the current
                    // possible year is lower than the next actual year.
                    if (allTimeframes[possibleTimeIndex].Value < timeframes[actualTimeIndex].Left.Value)
                    {
                        // Need to pad with a blank, so just let the blank layer object be added.
                        // Increment the possible time index to the next timeframe.
                        possibleTimeIndex++;
                    }
                    else
                    {
                        NycTimeframe time = timeframes[actualTimeIndex].Left;

                        // I think this check is no longer necessary, since we're probably
                        // always excluding unavailable data.  But if it ain't broke...
                        if (results.DataAvailableByTime[actualTimeIndex])
                        {
                            layer.Config                = new Dictionary <string, object>();
                            layer.Config["layers"]      = layerName;
                            layer.Config["styles"]      = "";
                            layer.Config["format"]      = "image/png";
                            layer.Config["tiled"]       = true;
                            layer.Config["srs"]         = "EPSG:4326";
                            layer.Config["transparent"] = true;
                            StringBuilder sb = new StringBuilder(sldHandlerUrl);
                            sb.Append("?indicator=").Append(indicatorId);
                            sb.Append("&resolution=").Append(resolutionType.ToString());
                            sb.Append("&time=").Append(time.UID);
                            if (scopeBorough != null)
                            {
                                sb.Append("&borough=").Append(scopeBorough);
                                if (scopeSubborough != null)
                                {
                                    sb.Append("&subborough=").Append(scopeSubborough);
                                }
                            }
                            layer.Config["SLD"] = sb.ToString();
                        }
                        // Increment both indexes.
                        possibleTimeIndex++;
                        actualTimeIndex++;
                    }

                    retVal.MapInfo.Layers.Add(layer);
                }

                // If we are creating layers, we must create a legend to describe them
                retVal.LegendInfo = GenerateLegendList(indicator, resolutionType);
            }
            return(retVal);
        }