local HttpService = game:GetService("HttpService")

-- Configuration
local CENTER_AT_ORIGIN = true -- Set this to false if you don't want to center the structures

-- Road color mapping
local ROAD_COLORS = {
	motorway = Color3.fromRGB(250, 158, 37),
	trunk = Color3.fromRGB(255, 195, 69),
	primary = Color3.fromRGB(255, 216, 97),
	secondary = Color3.fromRGB(255, 255, 179),
	tertiary = Color3.fromRGB(255, 255, 255),
	residential = Color3.fromRGB(223, 223, 223),
	unclassified = Color3.fromRGB(191, 191, 191),
	default = Color3.fromRGB(168, 168, 168)
}

-- Terrain color and height mapping
local TERRAIN_COLORS = {
	water = { color = Color3.fromRGB(0, 0, 255), height = 0 },
	grass = { color = Color3.fromRGB(0, 255, 0), height = 0 },
	forest = { color = Color3.fromRGB(34, 139, 34), height = 0 },
	residential = { color = Color3.fromRGB(223, 223, 223), height = 0 },
}

-- Function to fetch data from Overpass Turbo API
local function fetchOverpassData(lat, lon, radius)
	local query = string.format([[
        [out:json];
        (
            way["building"]["height"](around:%d,%f,%f);
            way["highway"](around:%d,%f,%f);
            way["natural"~"water|wood"](around:%d,%f,%f);
            way["landuse"](around:%d,%f,%f);
        );
        out geom;
    ]], radius, lat, lon, radius, lat, lon, radius, lat, lon, radius, lat, lon)

	local url = "https://overpass-api.de/api/interpreter"
	local response = HttpService:PostAsync(url, query)
	return HttpService:JSONDecode(response)
end

-- Function to create a wall between two points
local function createWall(point1, point2, height, parent)
	local wall = Instance.new("Part")
	wall.Anchored = true
	wall.Material = Enum.Material.Concrete
	wall.Color = Color3.new(0.8, 0.8, 0.8)

	local distance = (point1 - point2).Magnitude
	wall.Size = Vector3.new(0.5, height, distance)

	local midpoint = (point1 + point2) / 2
	local direction = (point2 - point1).Unit
	local rightVector = Vector3.new(-direction.Z, 0, direction.X)

	wall.CFrame = CFrame.fromMatrix(midpoint + Vector3.new(0, height/2, 0), rightVector, Vector3.new(0, 1, 0), -direction)
	wall.Parent = parent
end

-- Function to sanitize attribute names
local function sanitizeAttributeName(name)
	-- Replace any non-alphanumeric character with underscore
	local sanitized = name:gsub("[^%w]", "_")

	-- Ensure the name starts with a letter
	if sanitized:match("^%d") then
		sanitized = "a_" .. sanitized
	end

	return sanitized
end

-- Function to safely set attributes
local function safeSetAttribute(instance, key, value)
	local sanitizedKey = sanitizeAttributeName(key)
	-- Convert value to string if it's not a number or boolean
	if type(value) ~= "number" and type(value) ~= "boolean" then
		value = tostring(value)
	end
	instance:SetAttribute(sanitizedKey, value)
end

-- Function to parse Overpass data and create buildings
local function createBuildings(data, offset)
	for _, element in ipairs(data.elements) do
		if element.type == "way" and element.tags.building and element.tags.height then
			local model = Instance.new("Model")
			model.Name = "Building"
			model.Parent = workspace

			-- Set attributes for all OSM tags
			for key, value in pairs(element.tags) do
				safeSetAttribute(model, key, value)
			end

			local points = {}
			for _, node in ipairs(element.geometry) do
				table.insert(points, Vector3.new(node.lon * 111000, 0, -node.lat * 111000) - offset)
			end

			local height = tonumber(element.tags.height)
			if not height then
				height = 10 -- Default height if not specified
			end

			-- Create walls
			for i = 1, #points do
				local point1 = points[i]
				local point2 = points[i % #points + 1]
				createWall(point1, point2, height, model)
			end
		end
	end
end

-- Function to parse Overpass data and create roads
local function createRoads(data, offset)
	for _, element in ipairs(data.elements) do
		if element.type == "way" and element.tags.highway then
			local model = Instance.new("Model")
			model.Name = "Road"
			model.Parent = workspace

			-- Set attributes for all OSM tags
			for key, value in pairs(element.tags) do
				safeSetAttribute(model, key, value)
			end

			local points = {}
			for _, node in ipairs(element.geometry) do
				table.insert(points, Vector3.new(node.lon * 111000, 0, -node.lat * 111000) - offset)
			end

			for i = 1, #points - 1 do
				local point1, point2 = points[i], points[i + 1]
				local road = Instance.new("Part")
				road.Anchored = true
				road.Material = Enum.Material.Asphalt
				road.Color = ROAD_COLORS[element.tags.highway] or ROAD_COLORS.default

				local distance = (point2 - point1).Magnitude
				road.Size = Vector3.new(5, 0.5, distance)

				local midpoint = (point1 + point2) / 2
				local direction = (point2 - point1).Unit
				local rightVector = Vector3.new(-direction.Z, 0, direction.X)

				road.CFrame = CFrame.fromMatrix(midpoint, rightVector, Vector3.new(0, 1, 0), -direction)
				road.Parent = model
			end
		end
	end
end

-- Function to create flat terrain
local function createTerrain(area, terrainType, offset)
	local terrainInfo = TERRAIN_COLORS[terrainType]
	if not terrainInfo then return end -- Skip if terrain type is not defined

	local points = {}
	for _, node in ipairs(area.geometry) do
		table.insert(points, Vector3.new(node.lon * 111000, terrainInfo.height, -node.lat * 111000) - offset)
	end

	-- Create a flat part for the terrain
	local terrainPart = Instance.new("Part")
	terrainPart.Anchored = true
	terrainPart.Material = Enum.Material.Grass -- You can change the material accordingly
	terrainPart.Color = terrainInfo.color
	terrainPart.Size = Vector3.new(0.5, 0.1, 0.5) -- Set size as needed
	terrainPart.Parent = workspace

	-- Set the position of the terrain part
	local minPoint = Vector3.new(math.huge, 0, math.huge)
	local maxPoint = Vector3.new(-math.huge, 0, -math.huge)

	for _, point in ipairs(points) do
		minPoint = Vector3.new(math.min(minPoint.X, point.X), terrainInfo.height, math.min(minPoint.Z, point.Z))
		maxPoint = Vector3.new(math.max(maxPoint.X, point.X), terrainInfo.height, math.max(maxPoint.Z, point.Z))
	end

	-- Adjust size and position based on min/max points
	terrainPart.Size = Vector3.new(maxPoint.X - minPoint.X + 1, 0.1, maxPoint.Z - minPoint.Z + 1)
	terrainPart.Position = (minPoint + maxPoint) / 2 + Vector3.new(0, 0.05, 0) -- Raise slightly above ground
end

-- Function to parse Overpass data and create terrain
local function createTerrainFromData(data, offset)
	for _, element in ipairs(data.elements) do
		if element.type == "way" then
			if element.tags.natural == "water" then
				createTerrain(element, "water", offset)
			elseif element.tags.natural == "wood" or element.tags.landuse == "forest" then
				createTerrain(element, "forest", offset)
			elseif element.tags.landuse == "grass" or element.tags.landuse == "residential" then
				createTerrain(element, "grass", offset)
			end
		end
	end
end

-- Function to calculate the center point of all structures
local function calculateCenter(data)
	local sumX, sumZ, count = 0, 0, 0
	for _, element in ipairs(data.elements) do
		if element.type == "way" and element.geometry then
			for _, node in ipairs(element.geometry) do
				sumX = sumX + (node.lon * 111000)
				sumZ = sumZ - (node.lat * 111000)
				count = count + 1
			end
		end
	end
	return Vector3.new(sumX / count, 0, sumZ / count)
end

-- Main function to fetch data and create structures
local function createStructures(lat, lon, radius)
	local data = fetchOverpassData(lat, lon, radius)

	local offset = Vector3.new(0, 0, 0)
	if CENTER_AT_ORIGIN then
		offset = calculateCenter(data)
	end

	createTerrainFromData(data, offset) -- Create terrain first
	createBuildings(data, offset) -- Then create buildings
	createRoads(data, offset) -- Finally create roads
end

-- Example usage
createStructures(25.772402, -80.193023, 1000) -- Example coordinates with a 8km radius


make this script a localscript, we will also need events, and a serverscript to manage https request, it should load buildings and roads around the player as they move, account for scaling