var importShapeFileToGeoJSONPromise = function(pathToShp){
    return importShapeStreamToGeoJSONPromise(fs.createReadStream(pathToShp), pathToShp)
}

var importShapeStreamToGeoJSONPromise = function(shapeStream, pathToShp, optAddStream){
    return new Promise(function(resolve, reject){
        var dataStr = "";
        var fileBaseName = path.basename(pathToShp);
        var extensionName = getFileExtensionOfFileName(pathToShp);
        if(extensionName.toUpperCase() == "JSON"){extensionName = "GEOJSON"}
        switch (extensionName.toUpperCase()){
            case "CSV" :
                shapeStream.on('data', function(chunk){
                    dataStr = dataStr + chunk;
                }).on('end', function(){
                    try{
                        var csvString = dataStr;
                        csv2geojson.csv2geojson(csvString, {delimiter: ","}, function(err, data) {
                            if(err){
                                console.log(err)
                                reject(err.map(function(curErr){return curErr.message}).join("; "))  
                            }else{
                                dlg_confirm("Import '" + fileBaseName + "' as Points or Line", "Your data can be interpreted as individual Point Features or a Line Feature consisting of the points. How do you want to import it?",function(){
                                    resolve(setGeoJsonRootNameNormalizeCrs(data, fileBaseName));
                                },function(){
                                    data = csv2geojson.toLine(data);
                                    resolve(setGeoJsonRootNameNormalizeCrs(data, fileBaseName));
                                },"Point Features", "Line Feature")
                            }
                        })
                    }catch(err){
                        console.log(err)
                        reject(err.message)  
                    }
                }).on('error', function(err){
                    reject(err.message)
                });
                
                break;
            case "TSV" :
                shapeStream.on('data', function(chunk){
                    dataStr = dataStr + chunk;
                }).on('end', function(){
                    try{
                        var csvString = dataStr;
                        csv2geojson.csv2geojson(csvString, {delimiter: "\t"}, function(err, data) {
                            if(err){
                                console.log(err)
                                reject(err.map(function(curErr){return curErr.message}).join("; "))  
                            }else{
                                dlg_confirm("Import '" + fileBaseName + "' as Points or Line", "Your data can be interpreted as individual Point Features or a Line Feature consisting of the points. How do you want to import it?",function(){
                                    resolve(setGeoJsonRootNameNormalizeCrs(data, fileBaseName));
                                },function(){
                                    data = csv2geojson.toLine(data);
                                    resolve(setGeoJsonRootNameNormalizeCrs(data, fileBaseName));
                                },"Point Features", "Line Feature")
                            }
                        })
                    }catch(err){
                        console.log(err)
                        reject(err.message)  
                    }
                }).on('error', function(err){
                    reject(err.message)
                });
                
                break;
            case "GEOJSON" :
                shapeStream.on('data', function(chunk){
                    dataStr = dataStr + chunk;
                }).on('end', function(){
                    try{
                        resolve(setGeoJsonRootNameNormalizeCrs(geojson_normalize(angular.fromJson(dataStr)), fileBaseName));
                    }catch(err){
                        console.log(err)
                        reject(err.message)
                    }
                }).on('error', function(err){
                    reject(err.message)
                });
                
                break;
            case "SHP" :
                if(!pathToShp){
                    reject("Please import .shp files via the Add File button of the browser.")
                }else{
                    shp(pathToShp.replace(".shp", "").replace(".SHP","")).then(function(data){
                        resolve(setGeoJsonRootNameNormalizeCrs(geojson_normalize(JSON.parse(JSON.stringify(data))), fileBaseName));
                    }).catch(function(err){
                        reject(err.message || err)
                    });
                }
                
                break;
            case "ZIP" :
                if(!pathToShp){
                    shp(shapeStream).then(function(data){
                        resolve(setGeoJsonRootNameNormalizeCrs(geojson_normalize(JSON.parse(JSON.stringify(data))), fileBaseName));
                    }).catch(function(err){
                        reject(err.message || err)
                    });
                }else{
                    shp(pathToShp.replace(".shp", "").replace(".SHP","")).then(function(data){
                        resolve(setGeoJsonRootNameNormalizeCrs(geojson_normalize(JSON.parse(JSON.stringify(data))), fileBaseName));
                    }).catch(function(err){
                        reject(err.message || err)
                    });
                }
                
                break;
            case "GPX" :
                shapeStream.on('data', function(chunk){
                    dataStr = dataStr + chunk;
                }).on('end', function(){
                    gpxStrToGeoJSONPromise(dataStr)
                    .then(function(data){
                        resolve(setGeoJsonRootNameNormalizeCrs(data, fileBaseName))
                    })
                    .catch(function(reason){
                        reject(reason.message || reason)
                    })
                }).on('error', function(err){
                    reject(err.message)
                });
                
                break;
            case "KML" :
                shapeStream.on('data', function(chunk){
                    dataStr = dataStr + chunk;
                }).on('end', function(){
                    console.log(dataStr)
                    kmlStrToGeoJSONPromise(dataStr)
                    .then(function(data){
                        resolve(setGeoJsonRootNameNormalizeCrs(data, fileBaseName))
                    })
                    .catch(function(reason){
                        reject(reason.message || reason)
                    })
                }).on('error', function(err){
                    reject(err.message)
                });
                break;
            case "KMZ" :
                shapeStream.pipe(unzipper.ParseOne(/\.kml$/))
                .on('data', function(chunk){
                    dataStr = dataStr + chunk;
                }).on('end', function(){
                    //console.log(dataStr)
                    kmlStrToGeoJSONPromise(dataStr)
                    .then(function(data){
                        resolve(setGeoJsonRootNameNormalizeCrs(data, fileBaseName))
                    })
                    .catch(function(reason){
                        reject(reason.message || reason)
                    })
                }).on('error', function(err){
                    reject(err.message)
                });
                break;
            default : 
                reject("file format not supported")
        }
        
    })
}

/*var importKmzStream = function(customStream, fileBaseName){
    var dataStr = ""
    return new Promise(function(resolve, reject){
        customStream.pipe(unzipper.ParseOne(/\.kml$/))
        .on('data', function(chunk){
            dataStr = dataStr + chunk;
        }).on('end', function(){
            console.log(dataStr)
            alert("hi")
            kmlStrToGeoJSONPromise(dataStr)
            .then(function(data){
                resolve(setGeoJsonRootNameNormalizeCrs(data, fileBaseName))
            })
            .catch(function(reason){
                reject(reason)
            })
        }).on('error', function(err){
            reject(err.message)
        });
            
    })
    
}*/

var setGeoJsonRootNameNormalizeCrs = function(geoJson, name){
    normalizeGeoJSONCrs(geoJson);
    if(!geoJson.properties){geoJson.properties = {};}
    if(!geoJson.properties.GEOlayersDisplayName){
        geoJson.properties.GEOlayersDisplayName = geoJson.properties.name = name;    
    }
    return geoJson;
}

var gpxStrToGeoJSONPromise = function(inputDataStr){
    return new Promise(function(resolve, reject){
        var gpx = new DOMParser().parseFromString(inputDataStr);
        var data = kmlGpxToGeojson.gpx(gpx);
        if(data){
            resolve(geojson_normalize(data));
        }else{
            reject("gpx couldn't be parsed")
        }
    })
}

var kmlStrToGeoJSONPromise = function(inputDataStr){
    return new Promise(function(resolve, reject){
        var kml = new DOMParser().parseFromString(inputDataStr);
        var data = kmlGpxToGeojson.kml(kml);
        if(data){
            resolve(geojson_normalize(data));
        }else{
            reject("kml couldn't be parsed")
        }
    })
}

var jumpToGeoJSONFeaturesPromise = function (mapcomp, features, dontChangeZoom, bufferOLUR){
    var bbox, OLUR
    if((features.type && features.type.toUpperCase() === "FEATURE")){
        features = [features];
    }
    if(dontChangeZoom){
        if(features.length === 1){
            OLUR = [[injectLatLonToFeature(features[0]).properties.lat, injectLatLonToFeature(features[0]).properties.lon]];
        }else{
            bbox = getBBoxFromGeoJSONFeatureAry(features);
            OLUR = [[bbox[0] + (bbox[2]-bbox[0])/2, bbox[1] + (bbox[3]-bbox[1])/2 ]] //only one point
        }
    }else{
        bbox = getBBoxFromGeoJSONFeatureAry(features);
        if(features.length === 1 && features[0].properties && features[0].properties.type === gl.currentViewBBoxTypeName){bufferOLUR = 0;}
        OLUR = convertBBoxGeoJSONtoGEOLAYERS(bbox);
    }
    return jumpToLatLongInputPromise(mapcomp, OLUR, bufferOLUR);
}

var getBBoxFromGeoJSONFeature = function (feature){
    if(!(feature.bbox instanceof Array)){
        feature.bbox = convertBBoxObjToGeoJSON(getBBfromGeoJSONGeometryOrFeatureObj(feature));
        
    }
    return feature.bbox
}

var getCrsFromGeoJSON = function(feature){
    var crsName = "WGS84";
    if(feature && feature.crs){
        switch(feature.crs.type){
            case "name":
                if(feature.crs.properties && feature.crs.properties.name){
                    crsName = feature.crs.properties.name;
                }
                break;
            case "link":
                //linked Crs
                console.log("linked crs not supported")
                break;
        }
    }
    return crsName;
}


var addGeoJsonBBox = function(bbox1, bbox2){
    return [Math.min(bbox1[0], bbox2[0]), Math.min(bbox1[1], bbox2[1]), Math.max(bbox1[2], bbox2[2]), Math.max(bbox1[3], bbox2[3])]
}

var getBBoxFromGeoJSONFeatureAry = function (features){
    var resultBBox = [90,180,-90,-180];
    features.forEach(function(feature){
        resultBBox = addGeoJsonBBox(resultBBox, getBBoxFromGeoJSONFeature(feature));
    })
    return resultBBox;
}

var getLineCoordinatesFromGeoJSONGeometryOrFeatureObj = function(geometryOrFeatureObj, lineCoordinates) {
    if(!lineCoordinates){lineCoordinates = [];}
    var geometryObj;
	if (typeof geometryOrFeatureObj.type == "string" && geometryOrFeatureObj.type.toUpperCase() == "FEATURE") {
		geometryObj = geometryOrFeatureObj.geometry;
	} else {
		geometryObj = geometryOrFeatureObj;
	}
	if (geometryObj) {
		var geometryType = undefined;
		
			geometryType = geometryObj.type.toUpperCase();
		
		if (geometryType == "GEOMETRYCOLLECTION") {
            var geometriesAry = geometryObj.geometries;
		} else {
			var geometriesAry = [geometryObj];
		}
        
		for (var gCollection = 0; gCollection < geometriesAry.length; gCollection++) {
			var curGeometryObj = geometriesAry[gCollection];
			if (typeof curGeometryObj.type == "string") {
				geometryType = curGeometryObj.type.toUpperCase();
			}
			switch (geometryType) {
				case "GEOMETRYCOLLECTION":
					getLineFeatureGeoJSONGeometryOrFeatureObj(curGeometryObj, lineCoordinates)
					break;
				case "POINT":
                    lineCoordinates.push(curGeometryObj.coordinates);
					break;
				case "LINESTRING":
					lineCoordinates = lineCoordinates.concat(curGeometryObj.coordinates);
					break;
				case "POLYGON":
					var polygonCoords = curGeometryObj.coordinates;
					for (var p = 0; p < polygonCoords.length; p++) {
                        lineCoordinates = lineCoordinates.concat(polygonCoords[p]);
					}
					break;
				case "MULTIPOINT":
					lineCoordinates = lineCoordinates.concat(curGeometryObj.coordinates);
					break;
				case "MULTILINESTRING":
                    var lineStrFeatures = curGeometryObj.coordinates.map(function(lineStringCoordinates){
                        return turf.lineString(lineStringCoordinates)
                    })
                    var fc=turf.featureCollection(lineStrFeatures);
                    var mergedFC = mergeGeoJSONlinestrings(angular.copy(fc))
					var polygonCoords = mergedFC.geometry.coordinates;
					for (var p = 0; p < polygonCoords.length; p++) {
						lineCoordinates = lineCoordinates.concat(polygonCoords[p]);
					}
					break;
				case "MULTIPOLYGON":
					var polygons = curGeometryObj.coordinates;
					for (var i = 0; i < polygons.length; i++) {
						var polygonCoords = polygons[i];
						for (var p = 0; p < polygonCoords.length; p++) {
							lineCoordinates = lineCoordinates.concat(polygonCoords[p]);
						}
					}
					break;
				default:
					return null
			}
		}
		return lineCoordinates
	}
}

var normalizeGeoJSONCrs = function(geoJSON){
    var crsFrom = getCrsFromGeoJSON(geoJSON)
    if(crsFrom.toUpperCase() != "WGS84" && crsFrom.toUpperCase() != "EPSG:4326"){
        var coordinateProjector
        try{
            coordinateProjector = proj4(proj4.defs(crsFrom)).inverse;
        }catch(err){
            console.log(err)
        }
        if(coordinateProjector){
            return reprojectGeoJSON(geoJSON, coordinateProjector);
        }
    }
    return geoJSON
}

var reprojectGeoJSON = function(geoJSON, coordinateProjector){
    getFeaturesAryFromGeoJsonObj(geoJSON).forEach(function(feature){
        reprojectGeoJSONFeature(feature, coordinateProjector);
    })
    return geoJSON
}


var reprojectGeoJSONFeature = function(geometryOrFeatureObj, coordinateProjector) {
	if (typeof geometryOrFeatureObj.type == "string" && geometryOrFeatureObj.type.toUpperCase() == "FEATURE") {
		geometryObj = geometryOrFeatureObj.geometry;
	} else {
		geometryObj = geometryOrFeatureObj;
	}
	if (geometryObj) {
        var geometryType = undefined;
		geometryType = geometryObj.type.toUpperCase();
		if (geometryType == "GEOMETRYCOLLECTION") {
            var geometriesAry = geometryObj.geometries;
		} else {
			var geometriesAry = [geometryObj];
		}
        for (var gCollection = 0; gCollection < geometriesAry.length; gCollection++) {
			var curGeometryObj = geometriesAry[gCollection];
			if (typeof curGeometryObj.type == "string") {
				geometryType = curGeometryObj.type.toUpperCase();
			}
            switch (geometryType) {
				case "GEOMETRYCOLLECTION":
					reprojectGeoJSONFeature(curGeometryObj, coordinateProjector)
					break;
				case "POINT":
                    curGeometryObj.coordinates = coordinateProjector(curGeometryObj.coordinates);
					break;
				case "LINESTRING":
                    curGeometryObj.coordinates = curGeometryObj.coordinates.map(function(coordinate){
                        return coordinateProjector(coordinate)
                    })
					break;
				case "POLYGON":
					var polygonCoords = curGeometryObj.coordinates;
					for (var p = 0; p < polygonCoords.length; p++) {
						polygonCoords[p] = polygonCoords[p].map(function(coordinate){
                            return coordinateProjector(coordinate)
                        })
					}
					break;
				case "MULTIPOINT":
					var polygonCoords = curGeometryObj.coordinates;
					for (var p = 0; p < polygonCoords.length; p++) {
						polygonCoords[p] = polygonCoords[p].map(function(coordinate){
                            return coordinateProjector(coordinate)
                        })
					}
					break;
				case "MULTILINESTRING":
					var polygonCoords = curGeometryObj.coordinates;
					for (var p = 0; p < polygonCoords.length; p++) {
						polygonCoords[p] = polygonCoords[p].map(function(coordinate){
                            return coordinateProjector(coordinate)
                        })
					}
					break;
				case "MULTIPOLYGON":
					var polygons = curGeometryObj.coordinates;
					for (var i = 0; i < polygons.length; i++) {
						var polygonCoords = polygons[i];
						for (var p = 0; p < polygonCoords.length; p++) {
							polygonCoords[p] = polygonCoords[p].map(function(coordinate){
                                return coordinateProjector(coordinate)
                            })
						}
					}
					break;
				default:
					return null
			}
		}
		return geometryOrFeatureObj
	}
}

var getBBfromGeoJSONGeometryOrFeatureObj = function(geometryOrFeatureObj, bb) {
	var BoundingBox = {
		xMin: -180,
		xMax: 180,
		yMin: -90,
		yMax: 90
	}
    var geometryObj;
	if (typeof geometryOrFeatureObj.type == "string" && geometryOrFeatureObj.type.toUpperCase() == "FEATURE") {
		geometryObj = geometryOrFeatureObj.geometry;
	} else {
		geometryObj = geometryOrFeatureObj;
	}
	if (geometryObj) {
		var geometryType = undefined;
		
			geometryType = geometryObj.type.toUpperCase();
		
		if (geometryType == "GEOMETRYCOLLECTION") {
            var geometriesAry = geometryObj.geometries;
		} else {
			var geometriesAry = [geometryObj];
		}
        var BoundingBox
		for (var gCollection = 0; gCollection < geometriesAry.length; gCollection++) {
			var curGeometryObj = geometriesAry[gCollection];
			if (typeof curGeometryObj.type == "string") {
				geometryType = curGeometryObj.type.toUpperCase();
			}
			switch (geometryType) {
				case "GEOMETRYCOLLECTION":
					getBBfromGeoJSONGeometryOrFeatureObj(curGeometryObj, bb)
					break;
				case "POINT":
					var convertedPoint = convertPointToPath(curGeometryObj.coordinates);
					bb = getBBfromPath(convertedPoint, bb);
					BoundingBox = bb;
					break;
				case "LINESTRING":
					bb = getBBfromPath(curGeometryObj.coordinates, bb);
					BoundingBox = bb;
					break;
				case "POLYGON":
					var polygonCoords = curGeometryObj.coordinates;
					for (var p = 0; p < polygonCoords.length; p++) {
						bb = getBBfromPath(polygonCoords[p], bb);
					}
					BoundingBox = bb;
					break;
				case "MULTIPOINT":
					var polygonCoords = curGeometryObj.coordinates;
					for (var p = 0; p < polygonCoords.length; p++) {
						bb = getBBfromPath(polygonCoords[p], bb);
					}
					BoundingBox = bb;
					break;
				case "MULTILINESTRING":
					var polygonCoords = curGeometryObj.coordinates;
					for (var p = 0; p < polygonCoords.length; p++) {
						bb = getBBfromPath(polygonCoords[p], bb);
					}
					BoundingBox = bb;
					break;
				case "MULTIPOLYGON":
					var polygons = curGeometryObj.coordinates;
					for (var i = 0; i < polygons.length; i++) {
						var polygonCoords = polygons[i];
						for (var p = 0; p < polygonCoords.length; p++) {
							bb = getBBfromPath(polygonCoords[p], bb);
						}
					}
					BoundingBox = bb;
					break;
				default:
					return null
			}
		}
		return BoundingBox
	}else{
        return [0,0,0,0]
    }
}
var getBBfromPath = function(Gpath, bb) {
	if (bb == undefined) {
		var bb = {
			xMin: Gpath[0][0],
			xMax: Gpath[0][0],
			yMin: Gpath[0][1],
			yMax: Gpath[0][1]
		}
	}
	for (var i = 0; i < Gpath.length; i++) {
		if (Gpath[i][0] < bb.xMin) {
			bb.xMin = Gpath[i][0];
		}
		if (Gpath[i][1] < bb.yMin) {
			bb.yMin = Gpath[i][1];
		}
		if (Gpath[i][0] > bb.xMax) {
			bb.xMax = Gpath[i][0];
		}
		if (Gpath[i][1] > bb.yMax) {
			bb.yMax = Gpath[i][1];
		}
	}
	return bb
}
var getBBfromGeoJSONGeometryOrFeatureObjAry = function(mapcomp, featuresAry) {
	var BoundingBox = undefined;
	for (var s = 0; s < featuresAry.length; s++) {
		if (featuresAry[s] != undefined) {
			BoundingBox = getBBfromGeoJSONGeometryOrFeatureObj(featuresAry[s], BoundingBox);
		}
	}
    return BoundingBox
}

var isVerticyInBB = function(Gpath, bb) {
	if (bb == undefined) {
		return false
	}
	for (var i = 0; i < Gpath.length; i++) {
		if (Gpath[i][0] >= bb.xMin && Gpath[i][0] <= bb.xMax && Gpath[i][1] >= bb.yMin && Gpath[i][1] <= bb.yMax) {
			return true
            break
		}
	}
	return false
}

var isVerticyOfGeoJSONGeometryOrFeatureObjAryInBB = function(mapcomp, featuresAry, BoundingBox) {
	for (var s = 0; s < featuresAry.length; s++) {
		if (featuresAry[s] != undefined) {
			if(isVerticyOfGeoJSONGeometryOrFeatureObjInBB(featuresAry[s], BoundingBox)){return true};
		}
	}
    return false
}

var isVerticyOfGeoJSONGeometryOrFeatureObjInBB = function(geometryOrFeatureObj, bb) {
	var geometryObj;
	if (typeof geometryOrFeatureObj.type == "string" && geometryOrFeatureObj.type.toUpperCase() == "FEATURE") {
		geometryObj = geometryOrFeatureObj.geometry;
	} else {
		geometryObj = geometryOrFeatureObj;
	}
	if (geometryObj) {
		var geometryType = undefined;
		
			geometryType = geometryObj.type.toUpperCase();
		
		if (geometryType == "GEOMETRYCOLLECTION") {
            var geometriesAry = geometryObj.geometries;
		} else {
			var geometriesAry = [geometryObj];
		}
        var BoundingBox
		for (var gCollection = 0; gCollection < geometriesAry.length; gCollection++) {
			var curGeometryObj = geometriesAry[gCollection];
			if (typeof curGeometryObj.type == "string") {
				geometryType = curGeometryObj.type.toUpperCase();
			}
			switch (geometryType) {
				case "GEOMETRYCOLLECTION":
					isVerticyOfGeoJSONGeometryOrFeatureObjInBB(curGeometryObj, bb)
					break;
				case "POINT":
					var convertedPoint = convertPointToPath(curGeometryObj.coordinates);
					if(isVerticyInBB(convertedPoint, bb)){return true};
					
					break;
				case "LINESTRING":
					if(isVerticyInBB(curGeometryObj.coordinates, bb)){return true};
					
					break;
				case "POLYGON":
					var polygonCoords = curGeometryObj.coordinates;
					for (var p = 0; p < polygonCoords.length; p++) {
						if(isVerticyInBB(polygonCoords[p], bb)){return true};
					}
					
					break;
				case "MULTIPOINT":
					var polygonCoords = curGeometryObj.coordinates;
					for (var p = 0; p < polygonCoords.length; p++) {
						if(isVerticyInBB(polygonCoords[p], bb)){return true};
					}
					
					break;
				case "MULTILINESTRING":
					var polygonCoords = curGeometryObj.coordinates;
					for (var p = 0; p < polygonCoords.length; p++) {
						if(isVerticyInBB(polygonCoords[p], bb)){return true};
					}
					
					break;
				case "MULTIPOLYGON":
					var polygons = curGeometryObj.coordinates;
					for (var i = 0; i < polygons.length; i++) {
						var polygonCoords = polygons[i];
						for (var p = 0; p < polygonCoords.length; p++) {
							if(isVerticyInBB(polygonCoords[p], bb)){return true};
						}
					}
					
					break;
				default:
					return false
			}
		}
		return false
	}else{
        return false
    }
}


var isFeaturePolygon = function(feature, noMulti){
    if(feature.geometry && feature.geometry.type){
        return (feature.geometry.type.toUpperCase() == "POLYGON" || (!noMulti && feature.geometry.type.toUpperCase() == "MULTIPOLYGON"))
    }else{
        return null    
    }
}
var isFeatureLinestring = function(feature, noMulti){
    if(feature.geometry && feature.geometry.type){
        return (feature.geometry.type.toUpperCase() == "LINESTRING" || (!noMulti && feature.geometry.type.toUpperCase() == "MULTILINESTRING"))
    }else{
        return null    
    }
}
var isFeaturePoint = function(feature, noMulti){
    if(feature.geometry && feature.geometry.type){
        return (feature.geometry.type.toUpperCase() == "POINT" || (!noMulti && feature.geometry.type.toUpperCase() == "MULTIPOINT"))
    }else{
        return null    
    }
}
var isFeatureGeometrycollection = function(feature){
    if(feature.geometry && feature.geometry.type){
        return (feature.geometry.type.toUpperCase() == "GEOMETRYCOLLECTION")
    }else{
        return null    
    }
}
var isFeaturecollection = function(feature){
    if(feature.type){
        return (feature.type.toUpperCase() == "FEATURECOLLECTION")
    }else{
        return null    
    }
}
var isFavoriteFeaturecollection = function(feature){
    if(feature.type){
        return (feature.type.toUpperCase() == "FEATURECOLLECTION" && feature.GEOlayersBrowserIcon === "favorites")
    }else{
        return null    
    }
}


var mergeGeoJSONFeatures = function(features, mergeWithTurf){
    var polygons = [];
    var lines = [];
    var others = [];
    var labelPlacement = [];
    features.forEach(function(feature){
        if(feature){
            switch (feature.geometry.type.toUpperCase()){
                case "POLYGON" :
                    polygons.push(feature);
                    break;
                case "MULTIPOLYGON" :
                    polygons.push(feature);
                    break;
                case "LINESTRING" :
                    lines.push(feature);
                    break;
                case "MULTILINESTRING" :
                    lines.push(feature);
                    break;
                default : 
                    if(feature.properties.label_placement === true || feature.properties.label_placement === "yes"){
                        labelPlacement.push(feature)
                    }else{
                        others.push(feature);
                    }  
            }
        }
    })
    var mergedPolygonfeature = null;
    var mergedLinefeature = null;
    if(polygons.length === 1){mergedPolygonfeature = polygons[0]}
    if(polygons.length > 1){
        var featureCollection = turf.featureCollection(polygons);
        featureCollection.properties = {
            name: polygons[0].name
        }
        if(mergeWithTurf){
            //featureCollection.features = [featureCollection.features[4]].concat(featureCollection.features)
            //featureCollection.features.splice(5,1)
            var onlyPolygons = [];
            var propsForMergedFeature = {};
            featureCollection.features.forEach(function(feature){
                angular.extend(propsForMergedFeature, feature.properties);
                if(feature.geometry.type.toUpperCase() == "POLYGON"){
                    onlyPolygons.push(feature);
                }else{
                    feature.geometry.coordinates.forEach(function(coordinate){
                        onlyPolygons.push(turf.polygon(coordinate))    
                    })
                    
                }
            })
            var featureCollection = turf.featureCollection(onlyPolygons);
            try{
                mergedPolygonfeature = turfmerge(featureCollection);
                mergedPolygonfeature.properties = propsForMergedFeature;
            }catch(err){
                console.log(err);
                mergedPolygonfeature = mergeGeoJSONPolygons(featureCollection);
            }
        }else{
            mergedPolygonfeature = mergeGeoJSONPolygons(featureCollection);
        }
    }
    if(lines.length === 1){mergedLinefeature = lines[0]}
    if(lines.length > 1){
        var featureCollection = turf.featureCollection(lines);
        featureCollection.properties = {
            name: lines[0].name
        }
        mergedLinefeature = mergeGeoJSONlinestrings(featureCollection);
    }
    if(labelPlacement.length > 0){
        if(mergedPolygonfeature){injectLabelPlacementToFeature(mergedPolygonfeature, labelPlacement[0])}
        if(mergedLinefeature){injectLabelPlacementToFeature(mergedLinefeature, labelPlacement[0])}
    }
    var featureAry = others
    if(mergedPolygonfeature){featureAry.push(mergedPolygonfeature)}
    if(mergedLinefeature){featureAry.push(mergedLinefeature)}
    if(featureAry.length > 1){
        console.log("downloading")
        console.log(featureAry.map(function(feature){return feature.geometry}))
        var newCollection, newGeometryCollection
        /*var newCollection = turf.featureCollection(featureAry);
        newCollection.properties = newCollection.features[0].properties;*/
        newGeometryCollection = {
            type:"GeometryCollection",
            geometries:featureAry.map(function(feature){return feature.geometry})
        }
        newCollection = {
            type:"Feature",
            properties:featureAry[0].properties,
            geometry:newGeometryCollection
        }
        /*try{
            
        newCollection = turf.geometrycollection(featureAry.map(function(feature){return feature.geometry}));
        }catch(err){console.log(err)}*/
        
        //console.log(newCollection)
        
        
        return newCollection;
    }else{
        return featureAry[0];
    }
}

var injectLabelPlacementToFeature = function(feature, labelPlacement){
    if(feature && labelPlacement && feature.properties && labelPlacement.geometry && labelPlacement.geometry.coordinates){
        feature.properties.lat = labelPlacement.geometry.coordinates[1];
        feature.properties.lon = labelPlacement.geometry.coordinates[0];    
    }
}

var turfmerge = function(polygons) {
    var merged = JSON.parse(JSON.stringify(polygons.features[0])),
        features = polygons.features;

    for (var i = 0, len = features.length; i < len; i++) {
        var poly = features[i];
        if (poly.geometry) {
            merged = turf.union(merged, poly);
        }
    }
    return merged;
}

var mergeGeoJSONPolygons = function(polygons){
    if(!polygons || polygons.features.length === 0){return null}
    //var props = {}
    var props = angular.copy(polygons.features.reduce(function(props, feature){
        return angular.extend(props, feature.properties)
    },{}));
    var topology = topojson_server.topology({collection:polygons}, {
        "property-transform" : function(feature) {
          return feature.properties;
        },
        "pre-quantization" : 1e7,
        "post-quantization" : 1e7
   }); 
    var geometriesAry = topology.objects.collection.geometries;
    var polygonGeometries = geometriesAry.filter(function(geometry){
        return (geometry.type.toUpperCase() === "POLYGON" || geometry.type.toUpperCase() === "MULTIPOLYGON")
    })
    var curGeometryCollection = topojson_client.merge(topology, polygonGeometries);

    //if(curGeometryCollection.geometry == 0
    var curFeature = {
        type: "Feature",
        properties: props,
        geometry: curGeometryCollection
    }
    return curFeature;
}

var mergeGeoJSONlinestrings = function(linestrings){
    if(!linestrings || linestrings.features.length === 0){return null}
    var props = angular.copy(linestrings.features[0].properties);
    var topology = topojson_server.topology({collection:linestrings}, {
        "property-transform" : function(feature) {
          return feature.properties;
        },
        "pre-quantization" : 1e7,
        "post-quantization" : 1e7
   }); 
    var geometriesAry = topology.objects.collection.geometries;
    var linestringGeometries = geometriesAry.filter(function(geometry){
        return (geometry.type.toUpperCase() === "LINESTRING" || geometry.type.toUpperCase() === "MULTILINESTRING")
    })
    var geometryCollectionForInput = {
        "type": "GeometryCollection",
        "properties": props,
        "geometries": linestringGeometries
    }
    var curGeometryCollection = topojson_client.mesh(topology, geometryCollectionForInput);
    //if(curGeometryCollection.geometry == 0
    var curFeature = {
        type: "Feature",
        properties: props,
        geometry: curGeometryCollection
    }
    return curFeature;
}

var mergeFeaturesByProp = function(features, propName, mergeWithTurf){
    var sortByProp = {}
    var featuresMergedByProp = []
    features.forEach(function(feature){
        if(feature.properties[propName] != undefined){
            if(sortByProp[feature.properties[propName]]){
                sortByProp[feature.properties[propName]].push(feature);
            }else{
                sortByProp[feature.properties[propName]] = [feature];
            }
        }else{
            featuresMergedByProp.push(feature)//push if propname has no value
        }
       
    })
    for(var id in sortByProp){
       if(sortByProp[id].length > 1){
           var mergedFeatures = mergeGeoJSONFeatures(sortByProp[id], mergeWithTurf);//can be undefined
           if(mergedFeatures){featuresMergedByProp.push(mergedFeatures)};
       }else{
           featuresMergedByProp.push(sortByProp[id][0]);
       }
    }
    return featuresMergedByProp
}

var sortByPropIntoFeatureCollections = function(geoJSON, getClassification){
    var featuresByAdminLevel = {};
    geoJSON.features.forEach(function(feature){
        var curAdminLevel = replaceAll((getClassification(feature) || "Feature_Collection"), " ", "_");
        if(featuresByAdminLevel[String(curAdminLevel)]){
            featuresByAdminLevel[String(curAdminLevel)].push(feature);
        }else{
            featuresByAdminLevel[String(curAdminLevel)] = [feature];
        }
    })
    var fcs = [];
    for(var adminLevel in featuresByAdminLevel){
        if(featuresByAdminLevel.hasOwnProperty(adminLevel)){
            var fc = turf.featureCollection(featuresByAdminLevel[adminLevel])
            fc.properties = {name: replaceAll(adminLevel, "_", " ")}
            fcs.push(fc)
        }
    }
    geoJSON = turf.featureCollection(fcs)
    return geoJSON
}