Skip to content

Commit

Permalink
Merge pull request #184 from milesap/master-v1.1.3
Browse files Browse the repository at this point in the history
Master v1.1.3
  • Loading branch information
Miles Petrov authored Mar 10, 2017
2 parents 93440d2 + 25b7370 commit eee3bb8
Show file tree
Hide file tree
Showing 7 changed files with 189 additions and 11 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "geoApi",
"version": "1.1.0",
"version": "1.1.3",
"description": "",
"main": "dist/geoapi.js",
"dependencies": {
Expand Down
2 changes: 1 addition & 1 deletion src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ function initAll(esriBundle, window) {
api.mapManager = mapManager(esriBundle, api);
api.mapPrint = mapPrint(esriBundle, api);
api.attribs = attribute(esriBundle, api);
api.symbology = symbology(window);
api.symbology = symbology(esriBundle, api, window);
api.hilight = hilight(esriBundle, api);
api.events = events();
api.query = query(esriBundle);
Expand Down
46 changes: 43 additions & 3 deletions src/layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -709,20 +709,60 @@ function serverLayerIdentifyBuilder(esriBundle) {

/**
* Performs in place assignment of integer ids for a GeoJSON FeatureCollection.
* Assumes all features have ids or all do not. May fail (create duplicate keys) if some do and some don't
* If at least one feature has an existing id outside the geoJson properties section,
* the original id value is copied in a newly created property ID_FILE of the properties object
* and the existing id value is replaced by an autogenerated number.
* Features without existing id from that same dataset will get a new properties ID_FILE
* with an empty string as value.
**************************************
* If at least one feature has an existing OBJECTID inside the geoJson properties section,
* the original OBJECTID value is copied in a newly created property OBJECTID_FILE of the properties object
* and the existing OBJECTID value is replaced by an autogenerated number.
* Features without existing OBJECTID from that same dataset will get a new properties OBJECTID_FILE
* with an empty string as value.
*/
function assignIds(geoJson) {
if (geoJson.type !== 'FeatureCollection') {
throw new Error('Assignment can only be performed on FeatureCollections');
}

let emptyID = true;
let emptyObjID = true;

// for every feature, if it does not have an id property, add it.
// 0 is not a valid object id
geoJson.features.forEach(function (val, idx) {
if (typeof val.id === 'undefined') {
val.id = idx + 1;
Object.assign(val.properties, { ID_FILE: '', OBJECTID_FILE: '' });

// to avoid double ID columns outside properties
if ('id' in val && typeof val.id !== 'undefined') {
val.properties.ID_FILE = val.id;
emptyID = false;
}

// to avoid double OBJECTID columns. Useful for both geojson and CSV file.
if ('OBJECTID' in val.properties) {
val.properties.OBJECTID_FILE = val.properties.OBJECTID;
delete val.properties.OBJECTID;
emptyObjID = false;
}

val.id = idx + 1;
});

// remove ID_FILE if all empty
if (emptyID) {
geoJson.features.forEach(function (val) {
delete val.properties.ID_FILE;
});
}

// remove OBJECTID_FILE if all empty
if (emptyObjID) {
geoJson.features.forEach(function (val) {
delete val.properties.OBJECTID_FILE;
});
}
}

/**
Expand Down
9 changes: 8 additions & 1 deletion src/layer/ogc.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,15 @@ function parseCapabilitiesBuilder(esriBundle) {
}

function getCapabilities() {
let url = wmsEndpoint;

// if url has a '?' do not append to avoid errors, user must add this manually
if (wmsEndpoint.indexOf('?') === -1) {
url += '?service=WMS&version=1.3&request=GetCapabilities';
}

return Promise.resolve(new esriBundle.esriRequest({
url: wmsEndpoint + '?service=WMS&version=1.3&request=GetCapabilities',
url,
handleAs: 'xml'
}).promise);
}
Expand Down
2 changes: 2 additions & 0 deletions src/legend.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
'use strict';
const TOO_MANY_LAYERS = 15;

// This file relates to legends on an exported map, not legends in the layer selector

/**
* Generate all permutations of length M, with exactly N `true` values.
*
Expand Down
112 changes: 107 additions & 5 deletions src/symbology.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,7 @@ function enhanceRenderer(renderer, legend) {
*/
function searchRenderer(attributes, renderer) {

// make an empty svg graphic as a default, in case nothing is found
let svgcode = svgjs(document.createElement('div')).svg();
let svgcode;
let symbol = {};

switch (renderer.type) {
Expand Down Expand Up @@ -147,7 +146,9 @@ function searchRenderer(attributes, renderer) {
// attempt to find the range our gVal belongs in
const cbi = renderer.classBreakInfos.find((cbi, index) => gVal > minSplits[index] &&
gVal <= cbi.classMaxValue);
if (!cbi) { break; } // outside of range on the high end
if (!cbi) { // outside of range on the high end
break;
}
svgcode = cbi.svgcode;
symbol = cbi.symbol;

Expand All @@ -160,6 +161,11 @@ function searchRenderer(attributes, renderer) {

}

// make an empty svg graphic in case nothing is found to avoid undefined inside the filters
if (typeof svgcode === 'undefined') {
svgcode = svgjs(document.createElement('div')).size(CONTAINER_SIZE, CONTAINER_SIZE).svg();
}

return { svgcode, symbol };

}
Expand Down Expand Up @@ -680,6 +686,101 @@ function buildRendererToLegend(window) {
};
}

/**
* Returns the legend information of an ESRI map service.
*
* @function getMapServerLegend
* @private
* @param {String} layerUrl service url (root service, not indexed endpoint)
* @param {Object} esriBundle collection of ESRI API objects
* @returns {Promise} resolves in an array of legend data
*
*/
function getMapServerLegend(layerUrl, esriBundle) {

// standard json request with error checking
const defService = esriBundle.esriRequest({
url: `${layerUrl}/legend`,
content: { f: 'json' },
callbackParamName: 'callback',
handleAs: 'json',
});

// wrap in promise to contain dojo deferred
return new Promise((resolve, reject) => {
defService.then(srvResult => {

if (srvResult.error) {
reject(srvResult.error);
} else {
resolve(srvResult);
}
}, error => {
reject(error);
});
});

}

/**
* Our symbology engine works off of renderers. When dealing with layers with no renderers,
* we need to take server-side legend and convert it to a fake renderer, which lets us
* leverage all the existing symbology code.
*
* @function mapServerLegendToRenderer
* @private
* @param {Object} serverLegend legend json from an esri map server
* @param {Integer} layerIndex the index of the layer in the legend we are interested in
* @returns {Object} a fake unique value renderer based off the legend
*
*/
function mapServerLegendToRenderer(serverLegend, layerIndex) {
const layerLegend = serverLegend.layers.find(l => {
return l.layerId === layerIndex;
});

// make the mock renderer
return {
type: 'uniqueValue',
uniqueValueInfos: layerLegend.legend.map(ll => {
return {
label: ll.label,
symbol: {
type: 'esriPMS',
imageData: ll.imageData,
contentType: ll.contentType
}
};
})
};
}

function buildMapServerToLocalLegend(esriBundle, geoApi) {
/**
* Orchestrator function that will:
* - Fetch a legend from an esri map server
* - Extract legend for a specific sub layer
* - Convert server legend to a temporary renderer
* - Convert temporary renderer to a viewer-formatted legend (return value)
*
* @function mapServerToLocalLegend
* @param {String} mapServerUrl service url (root service, not indexed endpoint)
* @param {Integer} layerIndex the index of the layer in the legend we are interested in
* @returns {Promise} resolves in a viewer-compatible legend for the given server and layer index
*
*/
return (mapServerUrl, layerIndex) => {
// get esri legend from server
return getMapServerLegend(mapServerUrl, esriBundle).then(serverLegendData => {
// derive renderer for specified layer
const fakeRenderer = mapServerLegendToRenderer(serverLegendData, layerIndex);

// convert renderer to viewer specific legend
return geoApi.symbology.rendererToLegend(fakeRenderer);
});
};
}

// TODO getZoomLevel should probably live in a file not named symbology
/**
* Takes the lod list and finds level as close to and above scale limit
Expand Down Expand Up @@ -715,14 +816,15 @@ function getZoomLevel(lods, maxScale) {
return currentLod;
}

module.exports = window => {
module.exports = (esriBundle, geoApi, window) => {
return {
getGraphicIcon,
getGraphicSymbol,
rendererToLegend: buildRendererToLegend(window),
generatePlaceholderSymbology,
generateWMSSymbology,
getZoomLevel,
enhanceRenderer
enhanceRenderer,
mapServerToLocalLegend: buildMapServerToLocalLegend(esriBundle, geoApi)
};
};
27 changes: 27 additions & 0 deletions test/testServerLegend.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Test Page</title>
<style>
.layerTile { position: absolute; }
</style>
</head>
<body>
<p id="mess" />
<script src="../dist/geoapi.js"></script>
<script>
// http://localhost:6002/test/testServerLegend.html

geoapi('http://js.arcgis.com/3.14/', window).then(function (api) {

var mahLegendPromise = api.symbology.mapServerToLocalLegend('http://www.agr.gc.ca/atlas/rest/services/imageservices/landuse_2010/ImageServer', 0);

mahLegendPromise.then(function (legend) {
console.log('this is my legend result', legend);
});

});
</script>
</body>
</html>

0 comments on commit eee3bb8

Please sign in to comment.