World Cities Database resources

I’m not sure if there’s already been a post on this type of stuff, but I’m just gonna post this anyway.

Hey Devforum! For the longest time, I’ve been trying to create a Real-Time Strategy Game (or RTS Game), but I’m lowkey just too lazy to actually finish one. Also, for me, it was pretty hard to get all of this stuff, so I’m going to put all of my Scripts and Models here for anybody who wants it.

World Cities Data ModuleScript

For anybody who doesn’t know what the World Cities Database is, it’s just data on (almost) every single city on Earth. By data, I’m talking about the city’s real-world position, its ruling country, etc.

Basically, I made a free model for the World Cities Database (free version) converted into a ModuleScript. You can get it here.

Country Capitals ModuleScript

This ModuleScript has (I think) every city’s capital. This ModuleScript was made with ChatGPT, so it also might not match with World Cities Database, but when I ran it, it was pretty good for the most part. Just thought I might include this ModuleScript too.

You can get the ModuleScript Model here.

Inserting the World Cities onto a Globe

I used ChatGPT to make this Script (because I have absolutely no idea how to actually do this myself), and it works pretty well.

If you want, you can uncomment the bottom part of the Script that creates a ClickDetector. The ClickDetector just prints out whatever city you clicked on and its properties (like population and stuff).

--// I highly recommend putting this into a Script inside of the ServerScriptService. You can delete this line if you want to.

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local WorldCities = require(--// The Path to your WorldCities ModuleScript. (example: ReplicatedStorage:WaitForChild("WorldCities"))
local CountryCapitals = require(--// The Path to your CountryCapitals ModuleScript. (example: ReplicatedStorage:WaitForChild("CountryCapitals"))

local globe = workspace:WaitForChild("Earth"):WaitForChild("Earth_Model"):WaitForChild("Earth") --// If you don't have an Earth Globe Model, you can use the one listed below.
local radius = globe.Size.X / 2

local longitudeOffset = 191.5

local CountryParts = Instance.new("Folder")
CountryParts.Name = "CountryParts"
CountryParts.Parent = workspace

local customRotation = {
	X = 0,
	Y = 90,
	Z = 90
}

local function latLonToPosition(lat, lon, radius)
	local latRad = math.rad(lat)
	local lonRad = math.rad(lon + longitudeOffset)

	local x = radius * math.cos(latRad) * math.sin(lonRad)
	local y = radius * math.sin(latRad)
	local z = radius * math.cos(latRad) * math.cos(lonRad)

	return Vector3.new(-x, y, -z)
end

-- Create city markers
for _, city in ipairs(WorldCities) do
	if city.population then
		local marker = Instance.new("Part")
		marker.Name = city.name
		marker.Anchored = true
		marker.CanCollide = false
		marker.Material = Enum.Material.Neon

		local isCapital = (CountryCapitals[city.country] == city.name)


		marker.Shape = Enum.PartType.Cylinder
		marker.BrickColor = BrickColor.new("Bright orange")

		if city.population >= 1000000 then
			marker.Size = Vector3.new(0.5, 1.2, 0.4)
		elseif city.population >= 300000 then
			marker.Size = Vector3.new(0.5, 0.9, 0.3)
		else
			marker.Size = Vector3.new(0.5, 0.6, 0.2)
		end

		local localPosition = latLonToPosition(city.lat, city.lon, radius + 0.1)
		local worldPosition = globe.Position + localPosition

		local up = (worldPosition - globe.Position).Unit
		local forward = up:Cross(Vector3.new(0, 1, 0)).Unit
		if forward.Magnitude == 0 then forward = Vector3.new(1, 0, 0) end
		local right = forward:Cross(up).Unit

		local baseCFrame = CFrame.fromMatrix(worldPosition, right, up)
		local rotationOffset = CFrame.Angles(
			math.rad(customRotation.X),
			math.rad(customRotation.Y),
			math.rad(customRotation.Z)
		)

		local finalCFrame = baseCFrame * rotationOffset
		local latMultiplier = math.abs(city.lat) / 90
		local outwardOffset = 0.5 + (latMultiplier * 0.5)
		local adjustedPosition = worldPosition + up * outwardOffset

		local baseCFrame = CFrame.fromMatrix(adjustedPosition, right, up)
		marker.CFrame = baseCFrame * CFrame.Angles(
			math.rad(customRotation.X),
			math.rad(customRotation.Y),
			math.rad(customRotation.Z)
		)

		local RulingCountryFolder = CountryParts:FindFirstChild(city.country)
		if not RulingCountryFolder then
			RulingCountryFolder = Instance.new("Folder")
			RulingCountryFolder.Name = city.country
			RulingCountryFolder.Parent = CountryParts
		end

		marker.Parent = RulingCountryFolder

--[[local clickDetector = Instance.new("ClickDetector")
clickDetector.MaxActivationDistance = 999999999
clickDetector.Parent = marker

clickDetector.MouseClick:Connect(function(player)
	print("City Name:", city.name)
	print("Ruling Country:", city.country)
	print("Population:", city.population)
	if isCapital then
		print("Capital")
	end
end)]]--
	end
end
Inserting the World Cities onto a Flat Map

I used ChatGPT for this, too, because I also don’t really know how to script this myself. It works fine, though.

--// I highly recommend putting this into a Script inside of the ServerScriptService. You can delete this line if you want to.

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local WorldCities = require(--// The path to your WorldCities ModuleScript. (example: ReplicatedStorage:WaitForChild("WorldCities"))
local CountryCapitals = require(--// The path to your CountryCapitals ModuleScript. (example: ReplicatedStorage:WaitForChild("CountryCapitals"))

local mapModel = workspace:WaitForChild("Map") --// If you don't have a Map Model, create a Model in the Workspace named "Map", then insert an Anchored Part with the size of whatever you want the Map to be.
task.wait()

-- Create a folder in Workspace to hold all countries
local countriesFolder = Instance.new("Folder")
countriesFolder.Name = "Countries"
countriesFolder.Parent = workspace

-- Create folders for each country based on CountryColors
local countryFolders = {}

-- Function to calculate map size properly
local function GetTrueBoundingBox(model)
	local minVec = Vector3.new(math.huge, math.huge, math.huge)
	local maxVec = Vector3.new(-math.huge, -math.huge, -math.huge)

	for _, part in ipairs(model:GetDescendants()) do
		if part:IsA("BasePart") then
			local cf = part.CFrame
			local size = part.Size
			local corners = {
				cf * Vector3.new(-size.X/2, -size.Y/2, -size.Z/2),
				cf * Vector3.new(-size.X/2, -size.Y/2,  size.Z/2),
				cf * Vector3.new(-size.X/2,  size.Y/2, -size.Z/2),
				cf * Vector3.new(-size.X/2,  size.Y/2,  size.Z/2),
				cf * Vector3.new( size.X/2, -size.Y/2, -size.Z/2),
				cf * Vector3.new( size.X/2, -size.Y/2,  size.Z/2),
				cf * Vector3.new( size.X/2,  size.Y/2, -size.Z/2),
				cf * Vector3.new( size.X/2,  size.Y/2,  size.Z/2),
			}
			for _, corner in ipairs(corners) do
				minVec = Vector3.new(
					math.min(minVec.X, corner.X),
					math.min(minVec.Y, corner.Y),
					math.min(minVec.Z, corner.Z)
				)
				maxVec = Vector3.new(
					math.max(maxVec.X, corner.X),
					math.max(maxVec.Y, corner.Y),
					math.max(maxVec.Z, corner.Z)
				)
			end
		end
	end

	local center = (minVec + maxVec) / 2
	local size = maxVec - minVec
	return CFrame.new(center), size
end

local mapCFrame, mapSize = GetTrueBoundingBox(mapModel)
local mapCenter = mapCFrame.Position

-- Coordinate bounds for the map
local lonMin, lonMax = -200, 160
local latMin, latMax = -70, 74

-- Population thresholds
local populationThreshold = 1000000000  -- Cities above this are large
local minPopulation = 50000           -- Cities below this are ignored

-- Size scaling
local smallSize = 0.5
local mediumSize = 1
local largeSize = 1.5

-- Place cities
for _, city in ipairs(WorldCities) do
	local lat = city.lat
	local lon = city.lon
	local population = city.population

	if population < minPopulation then
		continue
	end

	local xRatio = (lon - lonMin) / (lonMax - lonMin)
	local zRatio = (latMax - lat) / (latMax - latMin)

	local xOffset = (xRatio - 0.5) * mapSize.X
	local zOffset = (zRatio - 0.5) * mapSize.Z
	local yOffset = 1

	local X_TWEAK = -(80)
	local Z_TWEAK = 19
	local Y_TWEAK = -(0.5)

	local X_TWEAK = -80
	local Z_TWEAK = 19
	local Y_TWEAK = -0.5

	local position = mapCenter + Vector3.new(xOffset + X_TWEAK, yOffset + Y_TWEAK, zOffset + Z_TWEAK)

	local dot
	local sizeMultiplier = smallSize

	if population >= populationThreshold then
		sizeMultiplier = largeSize
		dot = Instance.new("Part")
		dot.Size = Vector3.new(sizeMultiplier, sizeMultiplier, sizeMultiplier)
		dot.Shape = Enum.PartType.Block
	elseif population >= 1000000 then
		sizeMultiplier = mediumSize
		dot = Instance.new("Part")
		dot.Size = Vector3.new(sizeMultiplier, sizeMultiplier, sizeMultiplier)
		dot.Shape = Enum.PartType.Block
	else
		dot = Instance.new("Part")
		dot.Size = Vector3.new(sizeMultiplier, sizeMultiplier, sizeMultiplier)
		dot.Shape = Enum.PartType.Block
	end

	dot.Position = position
	dot.Anchored = true
	dot.Material = Enum.Material.Neon
	dot.Name = city.name

	local countryColor = BrickColor.new("Bright orange")
	dot.BrickColor = countryColor

	-- Assign to country folder if it exists
	local countryFolder = countryFolders[city.country]
	if countryFolder then
		dot.Parent = countryFolder
	else
		dot.Parent = countriesFolder -- fallback
	end

	-- Check if the city is a capital and make the part a square and bright orange with smaller size
	if CountryCapitals[city.country] == city.name then
		dot.Size = Vector3.new(2, 2, 2)  -- Reduced size for capitals
		dot.BrickColor = BrickColor.new("Bright orange")  -- Set color to bright orange
		dot.Shape = Enum.PartType.Block  -- Ensure it's a block shape
	end

	local clickDetector = Instance.new("ClickDetector")
	clickDetector.Parent = dot
	clickDetector.MaxActivationDistance = 9999999999
	clickDetector.MouseClick:Connect(function()
		if CountryCapitals[city.country] == city.name then
			print("You clicked on:", city.name .. ", the Capital of " .. city.country .. ", with a Population of about " .. city.population)
		else
			print("You clicked on:", city.name .. ", " .. city.country .. ", with a Population of about " .. city.population)
		end
	end)
end
Earth Globe Model

Here is a Model of Earth I made a while ago. To insert cities on this Globe Model, you need to use the “Inserting the World Cities onto a Globe” section of this post.

If you have any questions/comments, feel free to comment them! Thanks for checking out my post!

5 Likes